diff --git a/.circleci/config.yml b/.circleci/config.yml index 696df67c9af4..8347ff534c47 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -70,6 +70,7 @@ commands: - "packages/hooks/node_modules" - "packages/cfd/node_modules" - "packages/indicators/node_modules" + - "packages/integration/node_modules" - "packages/p2p/node_modules" - "packages/reports/node_modules" - "packages/shared/node_modules" diff --git a/.gitignore b/.gitignore index 53be93a5a629..d2d2ebaf413c 100644 --- a/.gitignore +++ b/.gitignore @@ -23,4 +23,6 @@ packages/appstore/lib/ packages/appstore/.out .env nx-cloud.env - +test-results/ +playwright-report/ +playwright/.cache/ diff --git a/end-to-end-test/.env.example b/end-to-end-test/.env.example deleted file mode 100644 index 063f3a2b06a2..000000000000 --- a/end-to-end-test/.env.example +++ /dev/null @@ -1,16 +0,0 @@ -# To have needed env variables you need to make a copy out of this file and rename it to `.env` -# you can change any of values listed here to your desired ones -# also you can pass any environment variable to this file and have access to them using `process.env.[ENVIRONMENT_VARIABLE_NAME] (eg. process.env.APPID) -ENDPOINT_PAGE_APP_STORE=false -ENDPOINT_PAGE_DBOT_DASHBOARD=false -ENDPOINT_PAGE_DEBUG_SERVICE_WORKER=false -APPID= -ENDPOINT= -APP_URL= -QA_EMAIL_INBOX_USER_NAME= -QA_EMAIL_INBOX_PASSWORD = -ACCOUNT_RESIDENCE_HIGH_RISK= -ACCOUNT_CITIZENSHIP_HIGH_RISK= -ACCOUNT_RESIDENCE_LOW_RISK= -ACCOUNT_CITIZENSHIP_LOW_RISK= -RISK_LEVEL=low_risk # This stores the risk level that can be either high_risk or low_risk diff --git a/end-to-end-test/.github/workflows/playwright.yml b/end-to-end-test/.github/workflows/playwright.yml deleted file mode 100644 index f5138e1f1c5d..000000000000 --- a/end-to-end-test/.github/workflows/playwright.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: Playwright Tests -on: - push: - branches: [ master ] - pull_request: - branches: [ master ] -jobs: - test: - timeout-minutes: 60 - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 - with: - node-version: 18 - - name: Install dependencies - run: npm ci - - name: Install Playwright Browsers - run: npx playwright install --with-deps - - name: Run Playwright tests - run: npx playwright test - - uses: actions/upload-artifact@v3 - if: always() - with: - name: playwright-report - path: playwright-report/ - retention-days: 30 diff --git a/end-to-end-test/.gitignore b/end-to-end-test/.gitignore deleted file mode 100644 index 75e854d8dcf7..000000000000 --- a/end-to-end-test/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -node_modules/ -/test-results/ -/playwright-report/ -/playwright/.cache/ diff --git a/end-to-end-test/README.md b/end-to-end-test/README.md deleted file mode 100644 index 8bd0390d5226..000000000000 --- a/end-to-end-test/README.md +++ /dev/null @@ -1,63 +0,0 @@ -# Deriv App End to End test - -This directory is just to implement and run the E2E tests over the app. - -This uses [playwright](https://playwright.dev/) as its main framework and you can simply write test with it. -We strongly suggest you to read the [documentation](https://playwright.dev/docs/intro) of playwright once before you get started. - -## Run the test cases - -To run the End to End test you need to first install playwright on your machine using: - -`npx install playwright` to install the dependencies on your machine. - -then - -`npx playwright test` to start the tests to run. (You also can pass `--debug` option to this command to it to run them in headless browser and check them visually separated by different browsers eg. Chromium, Firefox or Edge) - -`npm run test:e2e` to run the tests from within the root `directory`. - -`npm run test:e2e-dev` to run the tests from within the root `directory` with trace enabled and show the test report. - -## Project structure - -### `.env` file - -It's mandatory to create this file to pass the needed environment variables to the framework to run the tests. The defined variables are these - -| # | Env Variable Name | Functionality | Type | Required | -| --- | ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | -------- | -| 1 | `ENDPOINT_PAGE_APP_STORE` | This variable enables the AppStore in the `AppURL/endpoint` page | Boolean | | -| 2 | `ENDPOINT_PAGE_DBOT_DASHBOARD` | enables the DBot dashboard in the `AppURL/endpoint` page | Boolean | | -| 3 | `ENDPOINT_PAGE_DEBUG_SERVICE_WORKER` | enables the Debug service worker in the `AppURL/endpoint` page | Boolean | | -| 4 | `APPID` | App ID of the qabox that we want to test with, On local machine it should be `9999` to redirect to localhost.binary.sx after sign up(Company convention) | Number | \* | -| 5 | `ENDPOINT` | Endpoint of qabox server | String | \* | -| 6 | `APP_URL` | App URL which tests should run on. Local machine URL is `localhost.binary.sx` | String | \* | -| 7 | `QA_EMAIL_INBOX_USER_NAME` | Username of qabox events page to retrive the signup email and enable the created account (You can find it in the LP under shared-fe folder with the `QA emails login creds` entry name) | String | \* | -| 8 | `QA_EMAIL_INBOX_PASSWORD` | Password of qabox events page to retrive the signup email and enable the created account (You can find it in the LP under shared-fe folder with the `QA emails login creds` entry name) | String | \* | -| 9 | `ACCOUNT_RESIDENCE_HIGH_RISK` | Account residence to use when creating high risk accounts it | String | \* | -| 10 | `ACCOUNT_RESIDENCE_LOW_RISK` | Account residence to use when creating low risk accounts | String | \* | -| 11 | `ACCOUNT_CITIZENSHIP_HIGH_RISK` | Account citizenship to use when creating high risk accounts | String | \* | -| 12 | `ACCOUNT_CITIZENSHIP_LOW_RISK` | Account citizenship to use when creating low risk accounts | String | \* | -| 13 | `RISK_LEVEL` | Risk level for the particular account i.e. high risk or low risk | String | \* | - -### `onboarding.ts` file - -This is the main components which runs before any test suit runs, the main goal of this is to create one account for all of the test cases, save it to a context and use the created context in all of the browsers and before all of the test cases start to run. -`global-setup.ts` runs the mentioned onboarding flow and save the state. Refer to `globalSetup` option in `plauwright.config.ts` to see how its bound to the app. - -So in each test suit that you want to write you need to worry that if you are logged in or not. You just need to get the page from `describe` method like below: - -``` -import { test, expect } from '@playwright/test'; - -test.describe('Change endpoint, Signup and login', () => { - test('Should change the endpoint and sign up', async ({ page }) => { - await page.goto(process.env.APP_URL!); - // now we are logged in to the app and ready to test. YAY! - await expect(page).toHaveTitle('Trader | Deriv'); - // created a new user account and logged in - // Also you can get access to created email account name using `process.env.email` - }); -}); -``` diff --git a/end-to-end-test/global-setup.ts b/end-to-end-test/global-setup.ts deleted file mode 100644 index 8ffa6efe69e2..000000000000 --- a/end-to-end-test/global-setup.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { chromium, FullConfig } from '@playwright/test'; -import OnboardingFlow from './tests/onboarding/onboarding'; - -async function globalSetup(config: FullConfig) { - const browser = await chromium.launch(); - const page = await browser.newPage({ - ignoreHTTPSErrors: true, - httpCredentials: { - username: `${process.env.QA_EMAIL_INBOX_USER_NAME}`, - password: `${process.env.QA_EMAIL_INBOX_PASSWORD}`, - }, - }); - const onboarding = new OnboardingFlow(page); - await onboarding.signUp(); - await page.context().storageState({ path: '/tmp/storage-state.json' }); - await browser.close(); -} -export default globalSetup; diff --git a/end-to-end-test/package.json b/end-to-end-test/package.json deleted file mode 100644 index ca933001abb8..000000000000 --- a/end-to-end-test/package.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "end-to-end-test", - "version": "1.0.0", - "description": "", - "scripts": { - "test": "npx playwright test" - }, - "keywords": [], - "author": "", - "license": "ISC", - "eslintConfig": { - "rules": { - "testing-library/no-await-sync-query": "off", - "testing-library/prefer-screen-queries": "off" - } - }, - "dependencies": { - "dotenv": "^16.0.3", - "@playwright/test": "^1.33.0" - } -} diff --git a/end-to-end-test/playwright.config.ts b/end-to-end-test/playwright.config.ts deleted file mode 100644 index 01f7c939eb17..000000000000 --- a/end-to-end-test/playwright.config.ts +++ /dev/null @@ -1,118 +0,0 @@ -import type { PlaywrightTestConfig } from '@playwright/test'; -import { devices } from '@playwright/test'; -import { config as dotenvConf } from 'dotenv'; - -/** - * Read environment variables from file. - * https://github.com/motdotla/dotenv - */ -dotenvConf(); -// globalSetup: process.env.CI ? undefined : require.resolve('./global-setup'), - -/** - * See https://playwright.dev/docs/test-configuration. - */ -const config: PlaywrightTestConfig = { - globalSetup: process.env.CI ? undefined : require.resolve('./global-setup'), - testDir: './tests', - /* Maximum time one test can run for. */ - timeout: 100 * 1000, - expect: { - /** - * Maximum time expect() should wait for the condition to be met. - * For example in `await expect(locator).toHaveText();` - */ - timeout: 5000, - }, - /* Run tests in files in parallel */ - fullyParallel: true, - /* Fail the build on CI if you accidentally left test.only in the source code. */ - forbidOnly: !!process.env.CI, - /* Retry on CI only */ - retries: process.env.CI ? 2 : 0, - /* Opt out of parallel tests on CI. */ - workers: process.env.CI ? 1 : undefined, - /* Reporter to use. See https://playwright.dev/docs/test-reporters */ - reporter: 'html', - /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ - use: { - storageState: '/tmp/storage-state.json', - ignoreHTTPSErrors: true, - - /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */ - actionTimeout: 0, - /* Base URL to use in actions like `await page.goto('/')`. */ - baseURL: process.env.CI ? process.env.test_link : 'https://localhost.binary.sx', - - /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ - trace: 'on-first-retry', - }, - - /* Configure projects for major browsers */ - projects: [ - { - name: 'chromium', - use: { - ignoreHTTPSErrors: true, - ...devices['Desktop Chrome'], - }, - }, - - // { - // name: 'firefox', - // use: { - // ignoreHTTPSErrors: true, - // ...devices['Desktop Firefox'], - // }, - // }, - - // { - // name: 'webkit', - // use: { - // ignoreHTTPSErrors: true, - // ...devices['Desktop Safari'], - // }, - // }, - - // /* Test against mobile viewports. */ - // // { - // // name: 'Mobile Chrome', - // // use: { - // // ...devices['Pixel 5'], - // // }, - // // }, - // // { - // // name: 'Mobile Safari', - // // use: { - // // ...devices['iPhone 12'], - // // }, - // // }, - - // /* Test against branded browsers. */ - // // { - // // name: 'Microsoft Edge', - // // use: { - // // channel: 'msedge', - // // }, - // // }, - // // { - // // name: 'Google Chrome', - // // use: { - // // channel: 'chrome', - // // }, - // // }, - ], - - /* Folder for test artifacts such as screenshots, videos, traces, etc. */ - // outputDir: 'test-results/', - - /* Run your local dev server before starting the tests */ - webServer: { - command: 'cd .. && npm run serve core', - url: 'https://localhost.binary.sx/', - ignoreHTTPSErrors: true, - reuseExistingServer: true, - }, -}; - -export default config; diff --git a/end-to-end-test/tests/change-endpoint/change-endpoint.tsx b/end-to-end-test/tests/change-endpoint/change-endpoint.tsx deleted file mode 100644 index 2ffea42a3803..000000000000 --- a/end-to-end-test/tests/change-endpoint/change-endpoint.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import { expect, Page } from '@playwright/test'; - -export default class ChangeEndpoint { - readonly page: Page; - - constructor(page: Page) { - this.page = page; - } - async cookieDialogHandler() { - const elem = this.page.locator('.cookie-banner'); - if (await elem.isVisible()) - await this.page.locator('.cookie-banner > button[type=submit]', { hasText: /Accept/ }).click(); - } - - async changeEndpoint() { - await this.page.goto(process.env.APP_URL!); - await expect(this.page).toHaveTitle('Trader | Deriv'); - await this.cookieDialogHandler(); - await this.page.goto(`${process.env.APP_URL!}/endpoint`); - await this.page.waitForSelector( - '#app_contents > .dc-themed-scrollbars > form > .dc-input:nth-child(2) > .dc-input__container > .dc-input__field' - ); - await this.page.click( - '#app_contents > .dc-themed-scrollbars > form > .dc-input:nth-child(2) > .dc-input__container > .dc-input__field' - ); - - await this.page.waitForSelector( - '#app_contents > .dc-themed-scrollbars > form > .dc-input:nth-child(2) > .dc-input__container > .dc-input__field' - ); - await this.page.click( - '#app_contents > .dc-themed-scrollbars > form > .dc-input:nth-child(2) > .dc-input__container > .dc-input__field' - ); - - await this.page.waitForSelector( - '#app_contents > .dc-themed-scrollbars > form > .dc-input:nth-child(2) > .dc-input__container > .dc-input__field' - ); - const first_input_selector = - '#app_contents > .dc-themed-scrollbars > form > .dc-input:nth-child(2) > .dc-input__container > .dc-input__field'; - await this.page.click(first_input_selector, { clickCount: 3 }); - await this.page.keyboard.press('Backspace'); - - await this.page.click(first_input_selector); - - await this.page.type(first_input_selector, process.env.ENDPOINT!); - const second_input_selector = - '#app_contents > .dc-themed-scrollbars > form > .dc-input:nth-child(3) > .dc-input__container > .dc-input__field'; - await this.page.click(second_input_selector, { clickCount: 3 }); - await this.page.keyboard.press('Backspace'); - await this.page.type(second_input_selector, process.env.APPID!); - if ( - !(await this.page.locator("input[name='is_appstore_enabled']").isChecked()) && - process.env.ENDPOINT_PAGE_APP_STORE === 'true' - ) { - await this.page.click('.dc-themed-scrollbars > form > div:nth-child(4) > .dc-checkbox > .dc-checkbox__box'); - } - if ( - !(await this.page.locator("input[name='show_dbot_dashboard']").isChecked()) && - process.env.ENDPOINT_PAGE_DBOT_DASHBOARD === 'true' - ) { - await this.page.click('.dc-themed-scrollbars > form > div:nth-child(5) > .dc-checkbox > .dc-checkbox__box'); - } - if ( - !(await this.page.locator("input[name='is_debug_service_worker_enabled']").isChecked()) && - process.env.ENDPOINT_PAGE_DEBUG_SERVICE_WORKER === 'true' - ) { - await this.page.click('.dc-themed-scrollbars > form > div:nth-child(6) > .dc-checkbox > .dc-checkbox__box'); - } - - await this.page.waitForSelector('#deriv_app > #app_contents > .dc-themed-scrollbars > form > .dc-btn--primary'); - await this.page.click('#deriv_app > #app_contents > .dc-themed-scrollbars > form > .dc-btn--primary'); - } -} diff --git a/end-to-end-test/tests/onboarding/onboarding.ts b/end-to-end-test/tests/onboarding/onboarding.ts deleted file mode 100644 index 0fa449f06e47..000000000000 --- a/end-to-end-test/tests/onboarding/onboarding.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { Page, expect, chromium } from '@playwright/test'; -import { suspend } from '../../utils'; -import ChangeEndpoint from '../change-endpoint/change-endpoint'; - -export default class OnboardingFlow { - readonly page: Page; - readonly ChangeEndpoint: ChangeEndpoint; - readonly email: string; - private signupPage: Page | null; - - constructor(page: Page) { - const randomString = new Date().getTime(); - this.page = page; - this.email = `deriv-fe-e2e-${randomString}@deriv.com`; - this.signupPage = null; - this.ChangeEndpoint = new ChangeEndpoint(page); - } - - async updateServerURLAndAppIDInLocalStorage() { - const server = process.env.ENDPOINT; - const app_id = process.env.APPID; - await this?.signupPage?.evaluate(`localStorage.setItem('config.server_url', '${server}');`); - await this?.signupPage?.evaluate(`localStorage.setItem('config.app_id', '${app_id}');`); - await this?.signupPage?.evaluate(`window.location.reload();`); - const server_url = await this?.signupPage?.evaluate(() => { - const result = localStorage.getItem('config.server_url'); - return Promise.resolve(result); - }); - expect(server_url).toBe(process.env.ENDPOINT); - await suspend(1000); - } - async demoWizardHandler() { - await this.page.locator('.static-dashboard-wrapper__header > h2', { hasText: 'CFDs' }); - await this.page.locator('.static-dashboard-wrapper__header > h2', { hasText: 'Multipliers' }); - await this.page.locator('button[type="submit"]', { hasText: 'Next' }).click(); - await this.page.locator('button[type="submit"]', { hasText: 'Next' }).click(); - await this.page.locator('button[type="submit"]', { hasText: 'Next' }).click(); - await this.page.locator('button[type="submit"]', { hasText: 'Next' }).click(); - } - async signUp() { - await this.ChangeEndpoint.changeEndpoint(); - await this.page.goto(process.env.APP_URL!); - await this.page.waitForSelector('#dt_signup_button'); - const [newPage] = await Promise.all([ - this.page.context().waitForEvent('page'), // get `context` by destructuring with `page` in the test params; 'page' is a built-in event, and **you must wait for this like this,**, or `newPage` will just be the response object, rather than an actual Playwright page object. - await this.page.click('#dt_signup_button'), - ]); - this.signupPage = newPage; - await suspend(10000); - await this.updateServerURLAndAppIDInLocalStorage(); - await this.signupPage.waitForLoadState(); - await this.signupPage.locator('input[name=email]#dm-email-input').isVisible(); - await this.signupPage.locator('input[name=email]#dm-email-input').type(this.email); - process.env.email = this.email; - await this.signupPage.locator('//*[@id="gatsby-focus-wrapper"]/main/section/form/div/label/div'); - await this.signupPage.locator('//*[@id="gatsby-focus-wrapper"]/main/section/form/div/label/div').click(); - await this.signupPage.waitForSelector('#dm-new-signup'); - await this.signupPage.click('#dm-new-signup'); - const browser = await chromium.launch(); - const mailPage = await browser.newPage({ - ignoreHTTPSErrors: true, - httpCredentials: { - username: `${process.env.QA_EMAIL_INBOX_USER_NAME}`, - password: `${process.env.QA_EMAIL_INBOX_PASSWORD}`, - }, - }); - await mailPage.goto(`https://${process.env.ENDPOINT!}/events`); - let hrefs = await mailPage.evaluate(() => { - return Array.from(document.links) - .filter(item => item.href.endsWith('_account_opening_new.html')) - .map(item => item.href); - }); - hrefs = hrefs.slice().reverse(); - // TODO need to find a better approach instead of this - // eslint-disable-next-line no-restricted-syntax - for await (const item of hrefs) { - await mailPage.goto(item); - if (await mailPage.getByText(this.email).isVisible()) { - const element = await mailPage.locator('a', { hasText: 'redirect?action=signup' }); - const val = await element.getAttribute('href'); - if (val) await this.page.goto(val); - await mailPage.close(); - await this.signupPage.close(); - break; - } - } - - this.page.waitForSelector('#dt_core_set-residence-form_signup-residence-select').then(async () => { - await this.page.click('#dt_core_set-residence-form_signup-residence-select'); - }); - - const RISK_LEVEL = process.env.RISK_LEVEL; - - const ACCOUNT_CITIZENSHIP = - (RISK_LEVEL === 'low_risk' - ? process.env.ACCOUNT_CITIZENSHIP_LOW_RISK - : process.env.ACCOUNT_CITIZENSHIP_HIGH_RISK) || ''; - - const ACCOUNT_RESIDENCE = - (RISK_LEVEL === 'low_risk' - ? process.env.ACCOUNT_RESIDENCE_LOW_RISK - : process.env.ACCOUNT_RESIDENCE_HIGH_RISK) || ''; - - await expect(this.page.getByText(ACCOUNT_RESIDENCE)).toBeVisible(); - await this.page.getByText(ACCOUNT_RESIDENCE).click(); - await this.page.click('#dt_core_set-citizenship-form_signup-citizenship-select'); - await expect(this.page.getByText(ACCOUNT_CITIZENSHIP)).toBeVisible(); - await this.page.getByText(ACCOUNT_CITIZENSHIP).click(); - await this.page.getByRole('dialog').getByRole('button', { name: 'Next' }).click(); - await expect(this.page.getByText(/Keep your account secure/)).toBeVisible(); - await this.page.locator('#dt_core_account-signup-modal_account-signup-password-field'); - await expect(this.page.getByText(/Start trading/)).toBeDisabled(); - await this.page.locator('#dt_core_account-signup-modal_account-signup-password-field').type('Abcd2134'); - await expect(this.page.getByText(/Start trading/)).toBeEnabled(); - await this.page.getByText(/Start trading/).click(); - if (this.page.url().includes('onboarding')) await this.demoWizardHandler(); - } -} diff --git a/end-to-end-test/tests/sample-test/buy.spec.tsx b/end-to-end-test/tests/sample-test/buy.spec.tsx deleted file mode 100644 index 99d20438e45b..000000000000 --- a/end-to-end-test/tests/sample-test/buy.spec.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { test, expect } from '@playwright/test'; - -test.describe('Should signup and logged in, in the app', () => { - test('Title should be "Trader | Deriv"', async ({ page }) => { - await page.goto(process.env.APP_URL!); - // now we are logged in to the app and ready to write tests - await expect(page).toHaveTitle('Trader | Deriv'); - }); -}); diff --git a/end-to-end-test/tests/traders-hub/dashboard-demo.spec.tsx b/end-to-end-test/tests/traders-hub/dashboard-demo.spec.tsx deleted file mode 100644 index 101b39872100..000000000000 --- a/end-to-end-test/tests/traders-hub/dashboard-demo.spec.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { test, expect } from '@playwright/test'; -import { TRADERS_HUB_URL, switchAccountType } from '../../utils'; - -test.describe("Trader's Hub Dashboard", () => { - test.beforeEach(async ({ page }) => { - await page.goto(TRADERS_HUB_URL); - }); - test('It should switch from Demo to Real', async ({ page }) => { - await switchAccountType(page, 'Demo', 'Real'); - const get_deriv_account_button = await page.getByText('Get a Deriv account').first(); - expect(get_deriv_account_button).toBeDefined(); - }); -}); diff --git a/end-to-end-test/tests/traders-hub/dashboard-real.spec.tsx b/end-to-end-test/tests/traders-hub/dashboard-real.spec.tsx deleted file mode 100644 index 46e30c3ce60c..000000000000 --- a/end-to-end-test/tests/traders-hub/dashboard-real.spec.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { test, expect } from '@playwright/test'; -import { TRADERS_HUB_URL, switchAccountType } from '../../utils'; - -test.describe("Trader's Hub Dashboard", () => { - const HIGH_RISK_MULTIPLIER_TEXT = 'text=Multipliers trading platform.'; - const LOW_RISK_MULTIPLIER_TEXT = 'text=Options and multipliers trading platform.'; - test.beforeEach(async ({ page }) => { - await page.goto(TRADERS_HUB_URL); - await switchAccountType(page, 'Demo', 'Real'); - }); - - test('It should switch from Real to Demo', async ({ page }) => { - await switchAccountType(page, 'Real', 'Demo'); - const demo_count = await page.getByText(/Demo/).count(); - expect(demo_count).toBeGreaterThanOrEqual(1); - }); - - test('It should have the regulator switcher for low risk and no regulator switcher for high risk', async ({ - page, - }) => { - const regulator_switcher = await page.getByText('Regulation:').count(); - if (process.env.RISK_LEVEL === 'low_risk') { - expect(regulator_switcher).toEqual(1); - } else { - expect(regulator_switcher).toEqual(0); - } - }); - - test('It should switch to EU for low risk', async ({ page }) => { - if (process.env.RISK_LEVEL === 'low_risk') { - await page.getByText('EU', { exact: true }).first().click(); - expect(await page.locator(HIGH_RISK_MULTIPLIER_TEXT)).toBeDefined(); - } - }); - - test('It should show Non-EU content for low risk and EU content for high risk on load', async ({ page }) => { - if (process.env.RISK_LEVEL === 'low_risk') { - expect(await page.locator(LOW_RISK_MULTIPLIER_TEXT)).toBeDefined(); - } else { - expect(await page.locator(HIGH_RISK_MULTIPLIER_TEXT)).toBeDefined(); - } - }); -}); diff --git a/end-to-end-test/utils/constants.ts b/end-to-end-test/utils/constants.ts deleted file mode 100644 index 18c6a8333b43..000000000000 --- a/end-to-end-test/utils/constants.ts +++ /dev/null @@ -1 +0,0 @@ -export const TRADERS_HUB_URL = `${process.env.APP_URL}/appstore/traders-hub`; diff --git a/end-to-end-test/utils/helpers.ts b/end-to-end-test/utils/helpers.ts deleted file mode 100644 index dd264f3d51fe..000000000000 --- a/end-to-end-test/utils/helpers.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Page } from '@playwright/test'; - -export const switchAccountType = async (page: Page, from_type: string, to_type: string) => { - await page.waitForLoadState('domcontentloaded'); - await page.waitForSelector('.account-type-dropdown--parent', { timeout: 0 }); - await page.waitForSelector(`.account-type-dropdown--${from_type.toLowerCase()}`, { timeout: 0 }); - await page.getByTestId('dti_dropdown_display').getByText(from_type).click({ timeout: 0 }); - await page.locator(`#${to_type.toLocaleLowerCase()}`).click(); - await page.waitForLoadState('domcontentloaded'); -}; diff --git a/end-to-end-test/utils/index.ts b/end-to-end-test/utils/index.ts deleted file mode 100644 index 0d04acd1c841..000000000000 --- a/end-to-end-test/utils/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { TRADERS_HUB_URL } from './constants'; -import { switchAccountType } from './helpers'; -import suspend from './suspend/suspend'; - -export { TRADERS_HUB_URL, switchAccountType, suspend }; diff --git a/jest.config.base.js b/jest.config.base.js index 51deb607f5e2..b9820f8cd0c0 100644 --- a/jest.config.base.js +++ b/jest.config.base.js @@ -12,4 +12,5 @@ module.exports = { transformIgnorePatterns: ['/node_modules/(?!@enykeev/react-virtualized).+\\.js$'], setupFiles: ['/../../jest.setup.js'], setupFilesAfterEnv: ['/../../setupTests.js'], + testPathIgnorePatterns: ['/integration-tests/'], }; diff --git a/package-lock.json b/package-lock.json index b3e7ffbd2337..7fe304f31424 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37,12 +37,14 @@ "@types/js-cookie": "^3.0.1", "@types/jsdom": "^20.0.0", "@types/loadjs": "^4.0.1", + "@types/lodash.debounce": "^4.0.7", "@types/lodash.merge": "^4.6.7", "@types/lodash.throttle": "^4.1.7", "@types/object.fromentries": "^2.0.0", "@types/qrcode.react": "^1.0.2", "@types/react-loadable": "^5.5.6", "@types/react-transition-group": "^4.4.4", + "@types/ws": "^8.5.5", "@xmldom/xmldom": "^0.8.4", "acorn": "^6.1.1", "babel-core": "^6.26.3", @@ -153,6 +155,7 @@ "sass-loader": "^12.6.0", "sass-resources-loader": "^2.1.1", "scratch-blocks": "0.1.0-prerelease.20200917235131", + "selfsigned": "^2.1.1", "shx": "^0.3.2", "source-map-loader": "^1.1.2", "style-loader": "^1.2.1", @@ -161,6 +164,7 @@ "svgo-loader": "^3.0.0", "terser-webpack-plugin": "^5.1.1", "typescript": "^4.6.3", + "usehooks-ts": "^2.7.0", "web-push-notifications": "^3.33.0", "webpack": "^5.81.0", "webpack-bundle-analyzer": "^4.3.0", @@ -170,6 +174,7 @@ "webpack-manifest-plugin": "^4.0.2", "webpack-node-externals": "^2.5.2", "workbox-webpack-plugin": "^6.0.2", + "ws": "^8.13.0", "yup": "^0.32.11" }, "devDependencies": { @@ -192,6 +197,7 @@ "@deriv/eslint-config-deriv": "^1.0.0-beta.3", "@jest/globals": "^26.5.3", "@nrwl/nx-cloud": "latest", + "@playwright/test": "^1.37.1", "@testing-library/jest-dom": "^5.12.0", "@testing-library/react": "^12.0.0", "@testing-library/react-hooks": "^7.0.2", @@ -1962,9 +1968,9 @@ } }, "node_modules/@babel/standalone": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/standalone/-/standalone-7.22.10.tgz", - "integrity": "sha512-VmK2sWxUTfDDh9mPfCtFJPIehZToteqK+Zpwq8oJUjJ+WeeKIFTTQIrDzH7jEdom+cAaaguU7FI/FBsBWFkIeQ==", + "version": "7.22.14", + "resolved": "https://registry.npmjs.org/@babel/standalone/-/standalone-7.22.14.tgz", + "integrity": "sha512-i61lDNe0nRm44nZj05g+1HNb0EVfDGaTI6PkoeUMUSel3GTG6T1OM3BdjZIXghnnFB8bSWbmfS1lkBQgNUdu5w==", "engines": { "node": ">=6.9.0" } @@ -2810,19 +2816,19 @@ } }, "node_modules/@datadog/browser-core": { - "version": "4.47.0", - "resolved": "https://registry.npmjs.org/@datadog/browser-core/-/browser-core-4.47.0.tgz", - "integrity": "sha512-C3qm4kiT8OoK09UnAed2HTY9ecDaz0n7Qm0m4WwC+lpwOR97oduWdggbvgJgLGdJLleQjqEFHyuB8BEvEQ66BQ==" + "version": "4.48.0", + "resolved": "https://registry.npmjs.org/@datadog/browser-core/-/browser-core-4.48.0.tgz", + "integrity": "sha512-Cs/mGnE+FV3cvJ4Zl9FMuZOltp2Gy4Ji4v1zgodp4XDxU/PLFKppzRLqsfE6NMBgUpQKK9mh9zk1tBZtpUVEjw==" }, "node_modules/@datadog/browser-logs": { - "version": "4.47.0", - "resolved": "https://registry.npmjs.org/@datadog/browser-logs/-/browser-logs-4.47.0.tgz", - "integrity": "sha512-leDi/qsQVLzWuVu1N1lSNs4mTb9CZ7/p8IMAO2WEg9yIHqTVUZG/+uxWyzbFdexpRo8KuKIBL1hvHQP4BAdwWQ==", + "version": "4.48.0", + "resolved": "https://registry.npmjs.org/@datadog/browser-logs/-/browser-logs-4.48.0.tgz", + "integrity": "sha512-Kha0lNj9rbooG1mX/gsktLrTRYzz/8WC0F0UqfO43MKvOREDgSkqWYAhR2A+91+y+Mvrm2UqwwD3Kv0ZRAGBzw==", "dependencies": { - "@datadog/browser-core": "4.47.0" + "@datadog/browser-core": "4.48.0" }, "peerDependencies": { - "@datadog/browser-rum": "4.47.0" + "@datadog/browser-rum": "4.48.0" }, "peerDependenciesMeta": { "@datadog/browser-rum": { @@ -2831,16 +2837,15 @@ } }, "node_modules/@datadog/browser-rum": { - "version": "4.47.0", - "resolved": "https://registry.npmjs.org/@datadog/browser-rum/-/browser-rum-4.47.0.tgz", - "integrity": "sha512-+CK8Z7w0fZnCN1oK7S3TQq9LfDmpXDnYxzmHw+11INHHdFhWqmUojpKKi+7+XLMaTOvU83yYBfOBFz2DSWvwew==", + "version": "4.48.0", + "resolved": "https://registry.npmjs.org/@datadog/browser-rum/-/browser-rum-4.48.0.tgz", + "integrity": "sha512-drN+r8JV2EUG5PFUPKJYIpKP7t/hjJmt8IrxfEZyzDErD0Z7imKpKaprjGeottR2XqHngm1+JDXgZTvHPqSNQg==", "dependencies": { - "@datadog/browser-core": "4.47.0", - "@datadog/browser-rum-core": "4.47.0", - "@datadog/browser-worker": "4.47.0" + "@datadog/browser-core": "4.48.0", + "@datadog/browser-rum-core": "4.48.0" }, "peerDependencies": { - "@datadog/browser-logs": "4.47.0" + "@datadog/browser-logs": "4.48.0" }, "peerDependenciesMeta": { "@datadog/browser-logs": { @@ -2849,25 +2854,17 @@ } }, "node_modules/@datadog/browser-rum-core": { - "version": "4.47.0", - "resolved": "https://registry.npmjs.org/@datadog/browser-rum-core/-/browser-rum-core-4.47.0.tgz", - "integrity": "sha512-6nOnFPZJ0cYkWV4w7lbtCxVGLiquxw68D1qvwXbMtxZ1q+zroLhPY+lpk7Hd6XN7OT7xmTP2wbTnr0DL5VGwig==", - "dependencies": { - "@datadog/browser-core": "4.47.0" - } - }, - "node_modules/@datadog/browser-worker": { - "version": "4.47.0", - "resolved": "https://registry.npmjs.org/@datadog/browser-worker/-/browser-worker-4.47.0.tgz", - "integrity": "sha512-7/jiPXiGYStd40zsQl0U6DBkkoKhFPuWgl5R/k4sKaMdZ3YXwhL3M+js7S7MIGsrNvpoZygEQml+McVYZ2Vmyg==", + "version": "4.48.0", + "resolved": "https://registry.npmjs.org/@datadog/browser-rum-core/-/browser-rum-core-4.48.0.tgz", + "integrity": "sha512-qrK55AcBQDzHcq/jHLlz58UeX7qEl/UZU4BVGt226KAhJrn+WQrtk0yzwnSTp9YjamxY0QP62Zu7mP7fAf7Nog==", "dependencies": { - "@datadog/browser-core": "4.47.0" + "@datadog/browser-core": "4.48.0" } }, "node_modules/@deriv/api-types": { - "version": "1.0.118", - "resolved": "https://registry.npmjs.org/@deriv/api-types/-/api-types-1.0.118.tgz", - "integrity": "sha512-+q/PEH/O3wRrJlDQWl8x03JCBhQQBZt343maDqDRAM//H2K7h4YT5ucnYHJiS+6xu5jGGeq7JR8lT4R14mfd3Q==" + "version": "1.0.121", + "resolved": "https://registry.npmjs.org/@deriv/api-types/-/api-types-1.0.121.tgz", + "integrity": "sha512-Sj42zIOj8Dbxe3nMN+Qf+tohlsKGRD6dw2lgzd835MDi9G/TZzVTsXJvzlJqvKSPSA94hIBWFc0PBOF6d0V+0g==" }, "node_modules/@deriv/deriv-api": { "version": "1.0.13", @@ -3085,9 +3082,9 @@ } }, "node_modules/@devtools-ds/themes/node_modules/@design-systems/utils/node_modules/@babel/runtime": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.10.tgz", - "integrity": "sha512-21t/fkKLMZI4pqP2wlmsQAWnYW1PDyKyyUV4vCi+B25ydmdaYTKXPwCj0BzSUnZf4seIiYvSA3jcZ3gdsMFkLQ==", + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.11.tgz", + "integrity": "sha512-ee7jVNlWN09+KftVOu9n7S8gQzD/Z6hN/I8VBRXW4P1+Xe7kJGXMwu8vds4aGIMHZnNbdpSWCfZZtinytpcAvA==", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -3373,11 +3370,11 @@ } }, "node_modules/@floating-ui/react-dom": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.1.tgz", - "integrity": "sha512-rZtAmSht4Lry6gdhAJDrCp/6rKN7++JnL1/Anbr/DdeyYXQPxvg/ivrbYvJulbRf4vL8b212suwMM2lxbv+RQA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.2.tgz", + "integrity": "sha512-5qhlDvjaLmAst/rKb3VdlCinwTF4EYMiVxuuc/HVUjs46W0zgtbMmAZ1UTsDrRTxRmUEzl92mOtWbeeXL26lSQ==", "dependencies": { - "@floating-ui/dom": "^1.3.0" + "@floating-ui/dom": "^1.5.1" }, "peerDependencies": { "react": ">=16.8.0", @@ -7242,6 +7239,25 @@ "typescript": "^3 || ^4" } }, + "node_modules/@playwright/test": { + "version": "1.37.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.37.1.tgz", + "integrity": "sha512-bq9zTli3vWJo8S3LwB91U0qDNQDpEXnw7knhxLM0nwDvexQAwx9tO8iKDZSqqneVq+URd/WIoz+BALMqUTgdSg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "playwright-core": "1.37.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, "node_modules/@pmmmwh/react-refresh-webpack-plugin": { "version": "0.5.11", "resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.11.tgz", @@ -8111,13 +8127,13 @@ } }, "node_modules/@sentry-internal/tracing": { - "version": "7.64.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.64.0.tgz", - "integrity": "sha512-1XE8W6ki7hHyBvX9hfirnGkKDBKNq3bDJyXS86E0bYVDl94nvbRM9BD9DHsCFetqYkVm1yDGEK+6aUVs4CztoQ==", + "version": "7.66.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.66.0.tgz", + "integrity": "sha512-3vCgC2hC3T45pn53yTDVcRpHoJTBxelDPPZVsipAbZnoOVPkj7n6dNfDhj3I3kwWCBPahPkXmE+R4xViR8VqJg==", "dependencies": { - "@sentry/core": "7.64.0", - "@sentry/types": "7.64.0", - "@sentry/utils": "7.64.0", + "@sentry/core": "7.66.0", + "@sentry/types": "7.66.0", + "@sentry/utils": "7.66.0", "tslib": "^2.4.1 || ^1.9.3" }, "engines": { @@ -8125,15 +8141,15 @@ } }, "node_modules/@sentry/browser": { - "version": "7.64.0", - "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-7.64.0.tgz", - "integrity": "sha512-lB2IWUkZavEDclxfLBp554dY10ZNIEvlDZUWWathW+Ws2wRb6PNLtuPUNu12R7Q7z0xpkOLrM1kRNN0OdldgKA==", - "dependencies": { - "@sentry-internal/tracing": "7.64.0", - "@sentry/core": "7.64.0", - "@sentry/replay": "7.64.0", - "@sentry/types": "7.64.0", - "@sentry/utils": "7.64.0", + "version": "7.66.0", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-7.66.0.tgz", + "integrity": "sha512-rW037rf8jkhyykG38+HUdwkRCKHJEMM5NkCqPIO5zuuxfLKukKdI2rbvgJ93s3/9UfsTuDFcKFL1u43mCn6sDw==", + "dependencies": { + "@sentry-internal/tracing": "7.66.0", + "@sentry/core": "7.66.0", + "@sentry/replay": "7.66.0", + "@sentry/types": "7.66.0", + "@sentry/utils": "7.66.0", "tslib": "^2.4.1 || ^1.9.3" }, "engines": { @@ -8141,12 +8157,12 @@ } }, "node_modules/@sentry/core": { - "version": "7.64.0", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.64.0.tgz", - "integrity": "sha512-IzmEyl5sNG7NyEFiyFHEHC+sizsZp9MEw1+RJRLX6U5RITvcsEgcajSkHQFafaBPzRrcxZMdm47Cwhl212LXcw==", + "version": "7.66.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.66.0.tgz", + "integrity": "sha512-WMAEPN86NeCJ1IT48Lqiz4MS5gdDjBwP4M63XP4msZn9aujSf2Qb6My5uT87AJr9zBtgk8MyJsuHr35F0P3q1w==", "dependencies": { - "@sentry/types": "7.64.0", - "@sentry/utils": "7.64.0", + "@sentry/types": "7.66.0", + "@sentry/utils": "7.66.0", "tslib": "^2.4.1 || ^1.9.3" }, "engines": { @@ -8154,32 +8170,32 @@ } }, "node_modules/@sentry/replay": { - "version": "7.64.0", - "resolved": "https://registry.npmjs.org/@sentry/replay/-/replay-7.64.0.tgz", - "integrity": "sha512-alaMCZDZhaAVmEyiUnszZnvfdbiZx5MmtMTGrlDd7tYq3K5OA9prdLqqlmfIJYBfYtXF3lD0iZFphOZQD+4CIw==", + "version": "7.66.0", + "resolved": "https://registry.npmjs.org/@sentry/replay/-/replay-7.66.0.tgz", + "integrity": "sha512-5Y2SlVTOFTo3uIycv0mRneBakQtLgWkOnsJaC5LB0Ip0TqVKiMCbQ578vvXp+yvRj4LcS1gNd98xTTNojBoQNg==", "dependencies": { - "@sentry/core": "7.64.0", - "@sentry/types": "7.64.0", - "@sentry/utils": "7.64.0" + "@sentry/core": "7.66.0", + "@sentry/types": "7.66.0", + "@sentry/utils": "7.66.0" }, "engines": { "node": ">=12" } }, "node_modules/@sentry/types": { - "version": "7.64.0", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.64.0.tgz", - "integrity": "sha512-LqjQprWXjUFRmzIlUjyA+KL+38elgIYmAeoDrdyNVh8MK5IC1W2Lh1Q87b4yOiZeMiIhIVNBd7Ecoh2rodGrGA==", + "version": "7.66.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.66.0.tgz", + "integrity": "sha512-uUMSoSiar6JhuD8p7ON/Ddp4JYvrVd2RpwXJRPH1A4H4Bd4DVt1mKJy1OLG6HdeQv39XyhB1lPZckKJg4tATPw==", "engines": { "node": ">=8" } }, "node_modules/@sentry/utils": { - "version": "7.64.0", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.64.0.tgz", - "integrity": "sha512-HRlM1INzK66Gt+F4vCItiwGKAng4gqzCR4C5marsL3qv6SrKH98dQnCGYgXluSWaaa56h97FRQu7TxCk6jkSvQ==", + "version": "7.66.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.66.0.tgz", + "integrity": "sha512-9GYUVgXjK66uXXcLXVMXVzlptqMtq1eJENCuDeezQiEFrNA71KkLDg00wESp+LL+bl3wpVTBApArpbF6UEG5hQ==", "dependencies": { - "@sentry/types": "7.64.0", + "@sentry/types": "7.66.0", "tslib": "^2.4.1 || ^1.9.3" }, "engines": { @@ -9595,9 +9611,9 @@ "integrity": "sha512-h4lTMgMJctJybDp8CQrxTUiiYmedihHWkjnF/8Pxseu2S6Nlfcy8kwboQ8yejh456rP2yWoEVm1sS/FVsfM48w==" }, "node_modules/@storybook/builder-webpack4/node_modules/@types/node": { - "version": "16.18.44", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.44.tgz", - "integrity": "sha512-PZXtT+wqSMHnLPVExTh+tMt1VK+GvjRLsGZMbcQ4Mb/cG63xJig/TUmgrDa9aborl2i22UnpIzHYNu7s97NbBQ==" + "version": "16.18.46", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.46.tgz", + "integrity": "sha512-Mnq3O9Xz52exs3mlxMcQuA7/9VFe/dXcrgAyfjLkABIqxXKOgBRjyazTxUbjsxDa4BP7hhPliyjVTP9RDP14xg==" }, "node_modules/@storybook/builder-webpack4/node_modules/@webassemblyjs/ast": { "version": "1.9.0", @@ -10890,9 +10906,9 @@ } }, "node_modules/@storybook/builder-webpack5/node_modules/@types/node": { - "version": "16.18.44", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.44.tgz", - "integrity": "sha512-PZXtT+wqSMHnLPVExTh+tMt1VK+GvjRLsGZMbcQ4Mb/cG63xJig/TUmgrDa9aborl2i22UnpIzHYNu7s97NbBQ==" + "version": "16.18.46", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.46.tgz", + "integrity": "sha512-Mnq3O9Xz52exs3mlxMcQuA7/9VFe/dXcrgAyfjLkABIqxXKOgBRjyazTxUbjsxDa4BP7hhPliyjVTP9RDP14xg==" }, "node_modules/@storybook/builder-webpack5/node_modules/colorette": { "version": "1.4.0", @@ -11317,9 +11333,9 @@ } }, "node_modules/@storybook/core-common/node_modules/@types/node": { - "version": "16.18.44", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.44.tgz", - "integrity": "sha512-PZXtT+wqSMHnLPVExTh+tMt1VK+GvjRLsGZMbcQ4Mb/cG63xJig/TUmgrDa9aborl2i22UnpIzHYNu7s97NbBQ==" + "version": "16.18.46", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.46.tgz", + "integrity": "sha512-Mnq3O9Xz52exs3mlxMcQuA7/9VFe/dXcrgAyfjLkABIqxXKOgBRjyazTxUbjsxDa4BP7hhPliyjVTP9RDP14xg==" }, "node_modules/@storybook/core-common/node_modules/@webassemblyjs/ast": { "version": "1.9.0", @@ -12141,9 +12157,9 @@ } }, "node_modules/@storybook/core-server/node_modules/@types/node": { - "version": "16.18.44", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.44.tgz", - "integrity": "sha512-PZXtT+wqSMHnLPVExTh+tMt1VK+GvjRLsGZMbcQ4Mb/cG63xJig/TUmgrDa9aborl2i22UnpIzHYNu7s97NbBQ==" + "version": "16.18.46", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.46.tgz", + "integrity": "sha512-Mnq3O9Xz52exs3mlxMcQuA7/9VFe/dXcrgAyfjLkABIqxXKOgBRjyazTxUbjsxDa4BP7hhPliyjVTP9RDP14xg==" }, "node_modules/@storybook/core-server/node_modules/@webassemblyjs/ast": { "version": "1.9.0", @@ -13064,9 +13080,9 @@ "integrity": "sha512-h4lTMgMJctJybDp8CQrxTUiiYmedihHWkjnF/8Pxseu2S6Nlfcy8kwboQ8yejh456rP2yWoEVm1sS/FVsfM48w==" }, "node_modules/@storybook/manager-webpack4/node_modules/@types/node": { - "version": "16.18.44", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.44.tgz", - "integrity": "sha512-PZXtT+wqSMHnLPVExTh+tMt1VK+GvjRLsGZMbcQ4Mb/cG63xJig/TUmgrDa9aborl2i22UnpIzHYNu7s97NbBQ==" + "version": "16.18.46", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.46.tgz", + "integrity": "sha512-Mnq3O9Xz52exs3mlxMcQuA7/9VFe/dXcrgAyfjLkABIqxXKOgBRjyazTxUbjsxDa4BP7hhPliyjVTP9RDP14xg==" }, "node_modules/@storybook/manager-webpack4/node_modules/@webassemblyjs/ast": { "version": "1.9.0", @@ -14344,9 +14360,9 @@ } }, "node_modules/@storybook/manager-webpack5/node_modules/@types/node": { - "version": "16.18.44", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.44.tgz", - "integrity": "sha512-PZXtT+wqSMHnLPVExTh+tMt1VK+GvjRLsGZMbcQ4Mb/cG63xJig/TUmgrDa9aborl2i22UnpIzHYNu7s97NbBQ==" + "version": "16.18.46", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.46.tgz", + "integrity": "sha512-Mnq3O9Xz52exs3mlxMcQuA7/9VFe/dXcrgAyfjLkABIqxXKOgBRjyazTxUbjsxDa4BP7hhPliyjVTP9RDP14xg==" }, "node_modules/@storybook/manager-webpack5/node_modules/ansi-styles": { "version": "4.3.0", @@ -14858,9 +14874,9 @@ "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==" }, "node_modules/@storybook/react/node_modules/@types/node": { - "version": "16.18.44", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.44.tgz", - "integrity": "sha512-PZXtT+wqSMHnLPVExTh+tMt1VK+GvjRLsGZMbcQ4Mb/cG63xJig/TUmgrDa9aborl2i22UnpIzHYNu7s97NbBQ==" + "version": "16.18.46", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.46.tgz", + "integrity": "sha512-Mnq3O9Xz52exs3mlxMcQuA7/9VFe/dXcrgAyfjLkABIqxXKOgBRjyazTxUbjsxDa4BP7hhPliyjVTP9RDP14xg==" }, "node_modules/@storybook/react/node_modules/acorn": { "version": "7.4.1", @@ -15931,15 +15947,23 @@ "license": "MIT" }, "node_modules/@types/loadjs": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@types/loadjs/-/loadjs-4.0.1.tgz", - "integrity": "sha512-UEMOleWwITwDvj+kI6T4etC9yMjmejH6UdJRAa1MWZwzDIW+Iz7T6z6Zc96N/5FeFsxAEBA/zP1ki+HluXPcHQ==" + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/loadjs/-/loadjs-4.0.2.tgz", + "integrity": "sha512-tsPs2Pv+T+MYU6wh+a5IZS9ryHrQza27j+yJ84yiPCLQgDjjM7aBfjAKPgDi9ks1x5vY0tESvux/SwHRAlMqLQ==" }, "node_modules/@types/lodash": { "version": "4.14.197", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.197.tgz", "integrity": "sha512-BMVOiWs0uNxHVlHBgzTIqJYmj+PgCo4euloGF+5m4okL3rEYzM2EEv78mw8zWSMM57dM7kVIgJ2QDvwHSoCI5g==" }, + "node_modules/@types/lodash.debounce": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@types/lodash.debounce/-/lodash.debounce-4.0.7.tgz", + "integrity": "sha512-X1T4wMZ+gT000M2/91SYj0d/7JfeNZ9PeeOldSNoE/lunLeQXKvkmIumI29IaKMotU/ln/McOIvgzZcQ/3TrSA==", + "dependencies": { + "@types/lodash": "*" + } + }, "node_modules/@types/lodash.merge": { "version": "4.6.7", "resolved": "https://registry.npmjs.org/@types/lodash.merge/-/lodash.merge-4.6.7.tgz", @@ -15974,7 +15998,7 @@ }, "node_modules/@types/minimist": { "version": "1.2.2", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/@types/node": { @@ -16033,22 +16057,22 @@ "license": "MIT" }, "node_modules/@types/q": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.5.tgz", - "integrity": "sha512-L28j2FcJfSZOnL1WBjDYp2vUHCeIFlyYI/53EwD/rKUBQ7MtUUfbQWiyKJGpcnv4/WgrhWsFKrcPstcAt/J0tQ==" + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.6.tgz", + "integrity": "sha512-IKjZ8RjTSwD4/YG+2gtj7BPFRB/lNbWKTiSj3M7U/TD2B7HfYCxvp2Zz6xA2WIY7pAuL1QOUPw8gQRbUrrq4fQ==" }, "node_modules/@types/qrcode.react": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@types/qrcode.react/-/qrcode.react-1.0.2.tgz", - "integrity": "sha512-I9Oq5Cjlkgy3Tw7krCnCXLw2/zMhizkTere49OOcta23tkvH0xBTP0yInimTh0gstLRtb8Ki9NZVujE5UI6ffQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/qrcode.react/-/qrcode.react-1.0.3.tgz", + "integrity": "sha512-gl7Ozf3BRQwfDUAU2xx7sWRBe/s7TqO0HAJukSQHbEVfQrFo5WKgZl0BHlN8u9W1DHXb4elgKRolHLZkgETXyA==", "dependencies": { "@types/react": "*" } }, "node_modules/@types/qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" + "version": "6.9.8", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.8.tgz", + "integrity": "sha512-u95svzDlTysU5xecFNTgfFG5RUWu1A9P0VzgpcIiGZA9iraHOdSzcxMxQ55DyeRaGCSxQi7LxXDI4rzq/MYfdg==" }, "node_modules/@types/reach__router": { "version": "1.3.11", @@ -16216,9 +16240,9 @@ "integrity": "sha512-NfQ4gyz38SL8sDNrSixxU2Os1a5xcdFxipAFxYEuLUlvU2uDwS4NUpsImcf1//SlWItCVMMLiylsxbmNMToV/g==" }, "node_modules/@types/uglify-js": { - "version": "3.17.1", - "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.17.1.tgz", - "integrity": "sha512-GkewRA4i5oXacU/n4MA9+bLgt5/L3F1mKrYvFGm7r2ouLXhRKjuWwo9XHNnbx6WF3vlGW21S3fCvgqxvxXXc5g==", + "version": "3.17.2", + "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.17.2.tgz", + "integrity": "sha512-9SjrHO54LINgC/6Ehr81NjAxAYvwEZqjUHLjJYvC4Nmr9jbLQCIZbWSvl4vXQkkmR1UAuaKDycau3O1kWGFyXQ==", "dependencies": { "source-map": "^0.6.1" } @@ -16273,6 +16297,14 @@ "node": ">= 8" } }, + "node_modules/@types/ws": { + "version": "8.5.5", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.5.tgz", + "integrity": "sha512-lwhs8hktwxSjf9UaZ9tG5M03PGogvFaH8gUgLNbN9HKIg0dvv6q+gkSuJ8HN4/VbyxkuLzCjlN7GquQ0gUJfIg==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/yargs": { "version": "15.0.14", "license": "MIT", @@ -17029,7 +17061,7 @@ }, "node_modules/abbrev": { "version": "1.1.1", - "devOptional": true, + "dev": true, "license": "ISC" }, "node_modules/accepts": { @@ -17147,7 +17179,7 @@ }, "node_modules/agentkeepalive": { "version": "4.2.1", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "debug": "^4.1.0", @@ -17358,7 +17390,7 @@ }, "node_modules/are-we-there-yet": { "version": "3.0.1", - "devOptional": true, + "dev": true, "license": "ISC", "dependencies": { "delegates": "^1.0.0", @@ -17562,13 +17594,13 @@ } }, "node_modules/array.prototype.reduce": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/array.prototype.reduce/-/array.prototype.reduce-1.0.5.tgz", - "integrity": "sha512-kDdugMl7id9COE8R7MHF5jWk7Dqt/fs4Pv+JXoICnYwqpjjjbUurz6w5fT5IG6brLdJhv6/VoHB0H7oyIBXd+Q==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/array.prototype.reduce/-/array.prototype.reduce-1.0.6.tgz", + "integrity": "sha512-UW+Mz8LG/sPSU8jRDCjVr6J/ZKAGpHfwrZ6kWTG5qCxIEiXdVshqGnu5vEZA8S1y6X4aCSbQZ0/EEsfvEvBiSg==", "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", "es-array-method-boxes-properly": "^1.0.0", "is-string": "^1.0.7" }, @@ -17590,9 +17622,28 @@ "get-intrinsic": "^1.1.3" } }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.1.tgz", + "integrity": "sha512-09x0ZWFEjj4WD8PDbykUwo3t9arLn8NIzmmYEJFpYekOAQjpkGSyrQhNoRTcwwcFRu+ycWF78QZ63oWTqSjBcw==", + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "get-intrinsic": "^1.2.1", + "is-array-buffer": "^3.0.2", + "is-shared-array-buffer": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/arrify": { "version": "1.0.1", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -17705,16 +17756,6 @@ } ] }, - "node_modules/async-foreach": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/async-foreach/-/async-foreach-0.1.3.tgz", - "integrity": "sha512-VUeSMD8nEGBWaZK4lizI1sf3yEC7pnAQ/mrI7pC2fBz2s/tq5jWWEngTwaf0Gruu/OoXRGLGg1XFqpYBiGTYJA==", - "optional": true, - "peer": true, - "engines": { - "node": "*" - } - }, "node_modules/async-limiter": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", @@ -19790,7 +19831,7 @@ }, "node_modules/camelcase-keys": { "version": "6.2.2", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "camelcase": "^5.3.1", @@ -19877,9 +19918,9 @@ } }, "node_modules/chai": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz", - "integrity": "sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==", + "version": "4.3.8", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.8.tgz", + "integrity": "sha512-vX4YvVVtxlfSZ2VecZgFUTU5qPCYsobVI2O9FmwEXBhDigYGQA6jRXCycIs1yJnnWbZ6/+a2zNIF5DfVCcJBFQ==", "peer": true, "dependencies": { "assertion-error": "^1.1.0", @@ -22562,7 +22603,7 @@ }, "node_modules/decamelize-keys": { "version": "1.1.1", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "decamelize": "^1.1.0", @@ -22577,7 +22618,7 @@ }, "node_modules/decamelize-keys/node_modules/map-obj": { "version": "1.0.1", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -23902,7 +23943,7 @@ }, "node_modules/env-paths": { "version": "2.2.1", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -23920,7 +23961,7 @@ }, "node_modules/err-code": { "version": "2.0.3", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/errno": { @@ -23950,17 +23991,18 @@ } }, "node_modules/es-abstract": { - "version": "1.21.2", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.2.tgz", - "integrity": "sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==", + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.1.tgz", + "integrity": "sha512-ioRRcXMO6OFyRpyzV3kE1IIBd4WG5/kltnzdxSCqoP8CMGs/Li+M1uF5o7lOkZVFjDs+NLesthnF66Pg/0q0Lw==", "dependencies": { "array-buffer-byte-length": "^1.0.0", + "arraybuffer.prototype.slice": "^1.0.1", "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.2", "es-set-tostringtag": "^2.0.1", "es-to-primitive": "^1.2.1", "function.prototype.name": "^1.1.5", - "get-intrinsic": "^1.2.0", + "get-intrinsic": "^1.2.1", "get-symbol-description": "^1.0.0", "globalthis": "^1.0.3", "gopd": "^1.0.1", @@ -23980,14 +24022,18 @@ "object-inspect": "^1.12.3", "object-keys": "^1.1.1", "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.4.3", + "regexp.prototype.flags": "^1.5.0", + "safe-array-concat": "^1.0.0", "safe-regex-test": "^1.0.0", "string.prototype.trim": "^1.2.7", "string.prototype.trimend": "^1.0.6", "string.prototype.trimstart": "^1.0.6", + "typed-array-buffer": "^1.0.0", + "typed-array-byte-length": "^1.0.0", + "typed-array-byte-offset": "^1.0.0", "typed-array-length": "^1.0.4", "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.9" + "which-typed-array": "^1.1.10" }, "engines": { "node": ">= 0.4" @@ -26932,15 +26978,15 @@ } }, "node_modules/fraction.js": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.1.tgz", - "integrity": "sha512-/KxoyCnPM0GwYI4NN0Iag38Tqt+od3/mLuguepLgCAKPn0ZhC544nssAW0tG2/00zXEYl9W+7hwAIpLHo6Oc7Q==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.4.tgz", + "integrity": "sha512-pwiTgt0Q7t+GHZA4yaLjObx4vXmmdcS0iSJ19o8d/goUGgItX9UZWKWNnLHehxviD8wU2IWRsnR8cD5+yOJP2Q==", "engines": { "node": "*" }, "funding": { "type": "patreon", - "url": "https://www.patreon.com/infusion" + "url": "https://github.com/sponsors/rawify" } }, "node_modules/fragment-cache": { @@ -27156,7 +27202,7 @@ }, "node_modules/gauge": { "version": "4.0.4", - "devOptional": true, + "dev": true, "license": "ISC", "dependencies": { "aproba": "^1.0.3 || ^2.0.0", @@ -27172,19 +27218,6 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "node_modules/gaze": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz", - "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==", - "optional": true, - "peer": true, - "dependencies": { - "globule": "^1.0.0" - }, - "engines": { - "node": ">= 4.0.0" - } - }, "node_modules/gensync": { "version": "1.0.0-beta.2", "license": "MIT", @@ -27209,12 +27242,13 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", - "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", + "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", "dependencies": { "function-bind": "^1.1.1", "has": "^1.0.3", + "has-proto": "^1.0.1", "has-symbols": "^1.0.3" }, "funding": { @@ -27719,55 +27753,6 @@ "dev": true, "license": "MIT" }, - "node_modules/globule": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/globule/-/globule-1.3.4.tgz", - "integrity": "sha512-OPTIfhMBh7JbBYDpa5b+Q5ptmMWKwcNcFSR/0c6t8V4f3ZAVBEsKNY37QdVqmLRYSMhOUGYrY0QhSoEpzGr/Eg==", - "optional": true, - "peer": true, - "dependencies": { - "glob": "~7.1.1", - "lodash": "^4.17.21", - "minimatch": "~3.0.2" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/globule/node_modules/glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", - "optional": true, - "peer": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/globule/node_modules/minimatch": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz", - "integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==", - "optional": true, - "peer": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/gonzales-pe": { "version": "4.3.0", "dev": true, @@ -27954,7 +27939,7 @@ }, "node_modules/hard-rejection": { "version": "2.1.0", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -28317,7 +28302,7 @@ }, "node_modules/hosted-git-info": { "version": "4.1.0", - "devOptional": true, + "dev": true, "license": "ISC", "dependencies": { "lru-cache": "^6.0.0" @@ -28328,7 +28313,7 @@ }, "node_modules/hosted-git-info/node_modules/lru-cache": { "version": "6.0.0", - "devOptional": true, + "dev": true, "license": "ISC", "dependencies": { "yallist": "^4.0.0" @@ -28716,7 +28701,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", - "devOptional": true + "dev": true }, "node_modules/http-deceiver": { "version": "1.2.7", @@ -28956,7 +28941,7 @@ }, "node_modules/humanize-ms": { "version": "1.2.1", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "ms": "^2.0.0" @@ -29806,7 +29791,7 @@ }, "node_modules/is-lambda": { "version": "1.0.1", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/is-lite": { @@ -33927,12 +33912,27 @@ "version": "4.0.8", "license": "MIT" }, + "node_modules/lodash.escape": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-4.0.1.tgz", + "integrity": "sha512-nXEOnb/jK9g0DYMr1/Xvq6l5xMD7GDG55+GSYIYmS0G4tBk/hURD4JR9WCavs04t33WmJx9kCyp9vJ+mr4BOUw==" + }, + "node_modules/lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==" + }, "node_modules/lodash.get": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", "peer": true }, + "node_modules/lodash.invokemap": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.invokemap/-/lodash.invokemap-4.6.0.tgz", + "integrity": "sha512-CfkycNtMqgUlfjfdh2BhKO/ZXrP8ePOX5lEU/g0R3ItJcnuxWDwokMGKx1hWcfOikmyOVx6X9IwWnDGlgKl61w==" + }, "node_modules/lodash.isfunction": { "version": "3.0.9", "dev": true, @@ -33966,6 +33966,11 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.pullall": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.pullall/-/lodash.pullall-4.2.0.tgz", + "integrity": "sha512-VhqxBKH0ZxPpLhiu68YD1KnHmbhQJQctcipvmFnqIBDYzcIHzf3Zpu0tpeOKtR4x76p9yohc506eGdOjTmyIBg==" + }, "node_modules/lodash.snakecase": { "version": "4.1.1", "dev": true, @@ -33994,6 +33999,11 @@ "version": "4.5.0", "license": "MIT" }, + "node_modules/lodash.uniqby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz", + "integrity": "sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww==" + }, "node_modules/lodash.upperfirst": { "version": "4.3.1", "dev": true, @@ -34325,7 +34335,7 @@ }, "node_modules/map-obj": { "version": "4.3.0", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -34838,7 +34848,7 @@ }, "node_modules/minimist-options": { "version": "4.1.0", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "arrify": "^1.0.1", @@ -34916,7 +34926,7 @@ }, "node_modules/minipass-sized": { "version": "1.0.3", - "devOptional": true, + "dev": true, "license": "ISC", "dependencies": { "minipass": "^3.0.0" @@ -35067,9 +35077,9 @@ } }, "node_modules/mobx": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/mobx/-/mobx-6.10.0.tgz", - "integrity": "sha512-WMbVpCMFtolbB8swQ5E2YRrU+Yu8iLozCVx3CdGjbBKlP7dFiCSuiG06uea3JCFN5DnvtAX7+G5Bp82e2xu0ww==", + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/mobx/-/mobx-6.10.2.tgz", + "integrity": "sha512-B1UGC3ieK3boCjnMEcZSwxqRDMdzX65H/8zOHbuTY8ZhvrIjTUoLRR2TP2bPqIgYRfb3+dUigu8yMZufNjn0LQ==", "funding": { "type": "opencollective", "url": "https://opencollective.com/mobx" @@ -35475,11 +35485,11 @@ } }, "node_modules/node-forge": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", - "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", "engines": { - "node": ">= 6.0.0" + "node": ">= 6.13.0" } }, "node_modules/node-gyp": { @@ -35691,469 +35701,9 @@ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==" }, - "node_modules/node-sass": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-7.0.3.tgz", - "integrity": "sha512-8MIlsY/4dXUkJDYht9pIWBhMil3uHmE8b/AdJPjmFn1nBx9X9BASzfzmsCy0uCCb8eqI3SYYzVPDswWqSx7gjw==", - "hasInstallScript": true, - "optional": true, - "peer": true, - "dependencies": { - "async-foreach": "^0.1.3", - "chalk": "^4.1.2", - "cross-spawn": "^7.0.3", - "gaze": "^1.0.0", - "get-stdin": "^4.0.1", - "glob": "^7.0.3", - "lodash": "^4.17.15", - "meow": "^9.0.0", - "nan": "^2.13.2", - "node-gyp": "^8.4.1", - "npmlog": "^5.0.0", - "request": "^2.88.0", - "sass-graph": "^4.0.1", - "stdout-stream": "^1.4.0", - "true-case-path": "^1.0.2" - }, - "bin": { - "node-sass": "bin/node-sass" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/node-sass/node_modules/@npmcli/fs": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz", - "integrity": "sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==", - "optional": true, - "peer": true, - "dependencies": { - "@gar/promisify": "^1.0.1", - "semver": "^7.3.5" - } - }, - "node_modules/node-sass/node_modules/@npmcli/move-file": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", - "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", - "deprecated": "This functionality has been moved to @npmcli/fs", - "optional": true, - "peer": true, - "dependencies": { - "mkdirp": "^1.0.4", - "rimraf": "^3.0.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/node-sass/node_modules/@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", - "optional": true, - "peer": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/node-sass/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "optional": true, - "peer": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/node-sass/node_modules/cacache": { - "version": "15.3.0", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz", - "integrity": "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==", - "optional": true, - "peer": true, - "dependencies": { - "@npmcli/fs": "^1.0.0", - "@npmcli/move-file": "^1.0.1", - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "glob": "^7.1.4", - "infer-owner": "^1.0.4", - "lru-cache": "^6.0.0", - "minipass": "^3.1.1", - "minipass-collect": "^1.0.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.2", - "mkdirp": "^1.0.3", - "p-map": "^4.0.0", - "promise-inflight": "^1.0.1", - "rimraf": "^3.0.2", - "ssri": "^8.0.1", - "tar": "^6.0.2", - "unique-filename": "^1.1.1" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/node-sass/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "optional": true, - "peer": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/node-sass/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "optional": true, - "peer": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/node-sass/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "optional": true, - "peer": true - }, - "node_modules/node-sass/node_modules/get-stdin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", - "integrity": "sha512-F5aQMywwJ2n85s4hJPTT9RPxGmubonuB10MNYo17/xph174n2MIR33HRguhzVag10O/npM7SPk73LMZNP+FaWw==", - "optional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/node-sass/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "optional": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/node-sass/node_modules/http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", - "optional": true, - "peer": true, - "dependencies": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/node-sass/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "optional": true, - "peer": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/node-sass/node_modules/make-fetch-happen": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz", - "integrity": "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==", - "optional": true, - "peer": true, - "dependencies": { - "agentkeepalive": "^4.1.3", - "cacache": "^15.2.0", - "http-cache-semantics": "^4.1.0", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", - "is-lambda": "^1.0.1", - "lru-cache": "^6.0.0", - "minipass": "^3.1.3", - "minipass-collect": "^1.0.2", - "minipass-fetch": "^1.3.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.2", - "promise-retry": "^2.0.1", - "socks-proxy-agent": "^6.0.0", - "ssri": "^8.0.0" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/node-sass/node_modules/meow": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz", - "integrity": "sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==", - "optional": true, - "peer": true, - "dependencies": { - "@types/minimist": "^1.2.0", - "camelcase-keys": "^6.2.2", - "decamelize": "^1.2.0", - "decamelize-keys": "^1.1.0", - "hard-rejection": "^2.1.0", - "minimist-options": "4.1.0", - "normalize-package-data": "^3.0.0", - "read-pkg-up": "^7.0.1", - "redent": "^3.0.0", - "trim-newlines": "^3.0.0", - "type-fest": "^0.18.0", - "yargs-parser": "^20.2.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/node-sass/node_modules/minipass-fetch": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.4.1.tgz", - "integrity": "sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==", - "optional": true, - "peer": true, - "dependencies": { - "minipass": "^3.1.0", - "minipass-sized": "^1.0.3", - "minizlib": "^2.0.0" - }, - "engines": { - "node": ">=8" - }, - "optionalDependencies": { - "encoding": "^0.1.12" - } - }, - "node_modules/node-sass/node_modules/node-gyp": { - "version": "8.4.1", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz", - "integrity": "sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==", - "optional": true, - "peer": true, - "dependencies": { - "env-paths": "^2.2.0", - "glob": "^7.1.4", - "graceful-fs": "^4.2.6", - "make-fetch-happen": "^9.1.0", - "nopt": "^5.0.0", - "npmlog": "^6.0.0", - "rimraf": "^3.0.2", - "semver": "^7.3.5", - "tar": "^6.1.2", - "which": "^2.0.2" - }, - "bin": { - "node-gyp": "bin/node-gyp.js" - }, - "engines": { - "node": ">= 10.12.0" - } - }, - "node_modules/node-sass/node_modules/node-gyp/node_modules/npmlog": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", - "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", - "optional": true, - "peer": true, - "dependencies": { - "are-we-there-yet": "^3.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^4.0.3", - "set-blocking": "^2.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/node-sass/node_modules/npmlog": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", - "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", - "optional": true, - "peer": true, - "dependencies": { - "are-we-there-yet": "^2.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^3.0.0", - "set-blocking": "^2.0.0" - } - }, - "node_modules/node-sass/node_modules/npmlog/node_modules/are-we-there-yet": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", - "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", - "optional": true, - "peer": true, - "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/node-sass/node_modules/npmlog/node_modules/gauge": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", - "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", - "optional": true, - "peer": true, - "dependencies": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.2", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.1", - "object-assign": "^4.1.1", - "signal-exit": "^3.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/node-sass/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "optional": true, - "peer": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/node-sass/node_modules/socks-proxy-agent": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz", - "integrity": "sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==", - "optional": true, - "peer": true, - "dependencies": { - "agent-base": "^6.0.2", - "debug": "^4.3.3", - "socks": "^2.6.2" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/node-sass/node_modules/ssri": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", - "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", - "optional": true, - "peer": true, - "dependencies": { - "minipass": "^3.1.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/node-sass/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "optional": true, - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/node-sass/node_modules/type-fest": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", - "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", - "optional": true, - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/node-sass/node_modules/unique-filename": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", - "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", - "optional": true, - "peer": true, - "dependencies": { - "unique-slug": "^2.0.0" - } - }, - "node_modules/node-sass/node_modules/unique-slug": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", - "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", - "optional": true, - "peer": true, - "dependencies": { - "imurmurhash": "^0.1.4" - } - }, - "node_modules/node-sass/node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "optional": true, - "peer": true, - "engines": { - "node": ">=10" - } - }, "node_modules/nopt": { "version": "5.0.0", - "devOptional": true, + "dev": true, "license": "ISC", "dependencies": { "abbrev": "1" @@ -36167,7 +35717,7 @@ }, "node_modules/normalize-package-data": { "version": "3.0.3", - "devOptional": true, + "dev": true, "license": "BSD-2-Clause", "dependencies": { "hosted-git-info": "^4.0.1", @@ -36181,7 +35731,7 @@ }, "node_modules/normalize-package-data/node_modules/lru-cache": { "version": "6.0.0", - "devOptional": true, + "dev": true, "license": "ISC", "dependencies": { "yallist": "^4.0.0" @@ -36192,7 +35742,7 @@ }, "node_modules/normalize-package-data/node_modules/semver": { "version": "7.3.8", - "devOptional": true, + "dev": true, "license": "ISC", "dependencies": { "lru-cache": "^6.0.0" @@ -38287,6 +37837,18 @@ "node": ">=8" } }, + "node_modules/playwright-core": { + "version": "1.37.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.37.1.tgz", + "integrity": "sha512-17EuQxlSIYCmEMwzMqusJ2ztDgJePjrbttaefgdsiqeLWidjYz9BxXaTaZWxH1J95SHGk6tjE+dwgWILJoUZfA==", + "dev": true, + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/please-upgrade-node": { "version": "3.2.0", "dev": true, @@ -39986,7 +39548,7 @@ }, "node_modules/promise-retry": { "version": "2.0.1", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "err-code": "^2.0.2", @@ -40016,13 +39578,13 @@ } }, "node_modules/promise.prototype.finally": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/promise.prototype.finally/-/promise.prototype.finally-3.1.4.tgz", - "integrity": "sha512-nNc3YbgMfLzqtqvO/q5DP6RR0SiHI9pUPGzyDf1q+usTwCN2kjvAnJkBb7bHe3o+fFSBPpsGMoYtaSi+LTNqng==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/promise.prototype.finally/-/promise.prototype.finally-3.1.5.tgz", + "integrity": "sha512-4TQ3Dk8yyUZGyU+UXInKdkQ2b6xtiBXAIScGAtGnXVmJtG1uOrxRgbF1ggIu72uzoWFzUfT3nUKa1SuMm9NBdg==", "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" }, "engines": { "node": ">= 0.4" @@ -40418,7 +39980,7 @@ }, "node_modules/quick-lru": { "version": "4.0.1", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -41754,7 +41316,7 @@ }, "node_modules/redent": { "version": "3.0.0", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "indent-string": "^4.0.0", @@ -41872,12 +41434,13 @@ "integrity": "sha512-jbD/FT0+9MBU2XAZluI7w2OBs1RBi6p9M83nkoZayQXXU9e8Robt69FcZc7wU4eJD/YFTjn1JdCk3rbMJajz8Q==" }, "node_modules/regexp.prototype.flags": { - "version": "1.4.3", - "license": "MIT", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", + "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==", "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "functions-have-names": "^1.2.2" + "define-properties": "^1.2.0", + "functions-have-names": "^1.2.3" }, "engines": { "node": ">= 0.4" @@ -42715,9 +42278,9 @@ } }, "node_modules/rudder-sdk-js": { - "version": "2.40.4", - "resolved": "https://registry.npmjs.org/rudder-sdk-js/-/rudder-sdk-js-2.40.4.tgz", - "integrity": "sha512-IK/XDL4x3LbRDFLb9zgTS+uYyX+ATdhvLjzsWeM6TBPdv9+FkUHzPvgzymDFHEt3x6OKm858LdE6AzBUQeO2yg==" + "version": "2.40.5", + "resolved": "https://registry.npmjs.org/rudder-sdk-js/-/rudder-sdk-js-2.40.5.tgz", + "integrity": "sha512-MGfRz/kocGlTzYYf/HNEfYEDHtKDT9jW4K6I8rcU/SsTeqEhoGXjnlvfeKkwUs9LqbuX+s4AC+AvSfUdBbSGYQ==" }, "node_modules/run-async": { "version": "2.4.1", @@ -43096,25 +42659,6 @@ "node": ">=14.0.0" } }, - "node_modules/sass-graph": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-4.0.1.tgz", - "integrity": "sha512-5YCfmGBmxoIRYHnKK2AKzrAkCoQ8ozO+iumT8K4tXJXRVCPf+7s1/9KxTSW3Rbvf+7Y7b4FR3mWyLnQr3PHocA==", - "optional": true, - "peer": true, - "dependencies": { - "glob": "^7.0.0", - "lodash": "^4.17.11", - "scss-tokenizer": "^0.4.3", - "yargs": "^17.2.1" - }, - "bin": { - "sassgraph": "bin/sassgraph" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/sass-loader": { "version": "12.6.0", "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-12.6.0.tgz", @@ -43330,27 +42874,6 @@ "resolved": "https://registry.npmjs.org/scrollparent/-/scrollparent-2.1.0.tgz", "integrity": "sha512-bnnvJL28/Rtz/kz2+4wpBjHzWoEzXhVg/TE8BeVGJHUqE8THNIRnDxDWMktwM+qahvlRdvlLdsQfYe+cuqfZeA==" }, - "node_modules/scss-tokenizer": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.4.3.tgz", - "integrity": "sha512-raKLgf1LI5QMQnG+RxHz6oK0sL3x3I4FN2UDLqgLOGO8hodECNnNh5BXn7fAyBxrA8zVzdQizQ6XjNJQ+uBwMw==", - "optional": true, - "peer": true, - "dependencies": { - "js-base64": "^2.4.9", - "source-map": "^0.7.3" - } - }, - "node_modules/scss-tokenizer/node_modules/source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", - "optional": true, - "peer": true, - "engines": { - "node": ">= 8" - } - }, "node_modules/seedrandom": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz", @@ -43368,11 +42891,14 @@ "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==" }, "node_modules/selfsigned": { - "version": "1.10.14", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.14.tgz", - "integrity": "sha512-lkjaiAye+wBZDCBsu5BGi0XiLRxeUlsGod5ZP924CRSEoGuZAw/f7y9RKu28rwTfiHVhdavhB0qH0INV6P1lEA==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.1.1.tgz", + "integrity": "sha512-GSL3aowiF7wa/WtSFwnUrludWFoNhftq8bUkH9pkzjpN2XSPOAYEgg6e0sS9s0rZwgJzJiQRPU18A6clnoW5wQ==", "dependencies": { - "node-forge": "^0.10.0" + "node-forge": "^1" + }, + "engines": { + "node": ">=10" } }, "node_modules/semver": { @@ -43791,13 +43317,13 @@ } }, "node_modules/sirv": { - "version": "1.0.19", - "resolved": "https://registry.npmjs.org/sirv/-/sirv-1.0.19.tgz", - "integrity": "sha512-JuLThK3TnZG1TAKDwNIqNq6QA2afLOCcm+iE8D1Kj3GA40pSPsxQjjJl0J8X3tsR7T+CP1GavpzLwYkgVLWrZQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.3.tgz", + "integrity": "sha512-O9jm9BsID1P+0HOi81VpXPoDxYP374pkOLzACAoyUQ/3OUVndNpsz6wMnY2z+yOxzbllCKZrM+9QrWsv4THnyA==", "dependencies": { "@polka/url": "^1.0.0-next.20", "mrmime": "^1.0.0", - "totalist": "^1.0.0" + "totalist": "^3.0.0" }, "engines": { "node": ">= 10" @@ -43859,7 +43385,7 @@ }, "node_modules/smart-buffer": { "version": "4.2.0", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">= 6.0.0", @@ -44101,7 +43627,7 @@ }, "node_modules/socks": { "version": "2.7.1", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "ip": "^2.0.0", @@ -44520,56 +44046,6 @@ "node": ">= 0.8" } }, - "node_modules/stdout-stream": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/stdout-stream/-/stdout-stream-1.4.1.tgz", - "integrity": "sha512-j4emi03KXqJWcIeF8eIXkjMFN1Cmb8gUlDYGeBALLPo5qdyTfA9bOtl8m33lRoC+vFMkP3gl0WsDr6+gzxbbTA==", - "optional": true, - "peer": true, - "dependencies": { - "readable-stream": "^2.0.1" - } - }, - "node_modules/stdout-stream/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "optional": true, - "peer": true - }, - "node_modules/stdout-stream/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "optional": true, - "peer": true, - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/stdout-stream/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "optional": true, - "peer": true - }, - "node_modules/stdout-stream/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "optional": true, - "peer": true, - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, "node_modules/stealthy-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", @@ -44763,13 +44239,13 @@ } }, "node_modules/string.prototype.padstart": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/string.prototype.padstart/-/string.prototype.padstart-3.1.4.tgz", - "integrity": "sha512-XqOHj8horGsF+zwxraBvMTkBFM28sS/jHBJajh17JtJKA92qazidiQbLosV4UA18azvLOVKYo/E3g3T9Y5826w==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/string.prototype.padstart/-/string.prototype.padstart-3.1.5.tgz", + "integrity": "sha512-R57IsE3JIfModQWrVXYZ8ZHWMBNDpIoniDwhYCR1nx+iHwDkjjk26a8xM9BYgf7SAXJO7sdNPng5J+0ccr5LFQ==", "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" }, "engines": { "node": ">= 0.4" @@ -46368,9 +45844,9 @@ "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==" }, "node_modules/totalist": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/totalist/-/totalist-1.1.0.tgz", - "integrity": "sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", "engines": { "node": ">=6" } @@ -46452,7 +45928,7 @@ }, "node_modules/trim-newlines": { "version": "3.0.1", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -46494,16 +45970,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/true-case-path": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-1.0.3.tgz", - "integrity": "sha512-m6s2OdQe5wgpFMC+pAJ+q9djG82O2jcHPOI6RNg1yy9rCYR+WD6Nbpl32fDpfC56nirdRy+opFa/Vk7HYhqaew==", - "optional": true, - "peer": true, - "dependencies": { - "glob": "^7.1.2" - } - }, "node_modules/ts-dedent": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz", @@ -46779,6 +46245,54 @@ "node": ">= 0.6" } }, + "node_modules/typed-array-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz", + "integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz", + "integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==", + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "has-proto": "^1.0.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz", + "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==", + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "has-proto": "^1.0.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/typed-array-length": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", @@ -47292,11 +46806,6 @@ "requires-port": "^1.0.0" } }, - "node_modules/url-resolve-browser": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/url-resolve-browser/-/url-resolve-browser-1.1.0.tgz", - "integrity": "sha512-vxgLZj8ysAZHOiZw6PUG7WGi28OvNqsaQdbZLq54lfZyUEdBGdkfOzc2p3RVhRgdojbsE6lA4Te02fwRyhPXfw==" - }, "node_modules/url-search-params-polyfill": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/url-search-params-polyfill/-/url-search-params-polyfill-7.0.1.tgz", @@ -47371,6 +46880,19 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/usehooks-ts": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/usehooks-ts/-/usehooks-ts-2.9.1.tgz", + "integrity": "sha512-2FAuSIGHlY+apM9FVlj8/oNhd+1y+Uwv5QNkMQz1oSfdHk4PXo1qoCw9I5M7j0vpH8CSWFJwXbVPeYDjLCx9PA==", + "engines": { + "node": ">=16.15.0", + "npm": ">=8" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/util": { "version": "0.11.1", "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", @@ -47960,14 +47482,13 @@ } }, "node_modules/web-push-notifications": { - "version": "3.33.0", - "resolved": "https://registry.npmjs.org/web-push-notifications/-/web-push-notifications-3.33.0.tgz", - "integrity": "sha512-9b86KArBi2INVVZQF50ed/ZkW4nUxijJBFmOPy26IEJENXytHUkesvKXGUulGqzbbkLEQRpE8NT0WS5l4RocNQ==", + "version": "3.34.0", + "resolved": "https://registry.npmjs.org/web-push-notifications/-/web-push-notifications-3.34.0.tgz", + "integrity": "sha512-AFwTc19amF57nn3KJ0QDlAVptAP+a1eoc7BwQG5aN//3mvqyexCNKV1Ui3s+e97FdM+qWdNHtoJSdZ1AZyBqSg==", "dependencies": { "@pushwoosh/logger": "1.0.6", "@pushwoosh/web-push-inbox-widget": "1.0.7", - "@pushwoosh/web-push-subscribe-popup": "1.0.11", - "url-resolve-browser": "1.1.0" + "@pushwoosh/web-push-subscribe-popup": "1.0.11" } }, "node_modules/webidl-conversions": { @@ -48025,19 +47546,26 @@ } }, "node_modules/webpack-bundle-analyzer": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.9.0.tgz", - "integrity": "sha512-+bXGmO1LyiNx0i9enBu3H8mv42sj/BJWhZNFwjz92tVnBa9J3JMGo2an2IXlEleoDOPn/Hofl5hr/xCpObUDtw==", + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.9.1.tgz", + "integrity": "sha512-jnd6EoYrf9yMxCyYDPj8eutJvtjQNp8PHmni/e/ulydHBWhT5J3menXt3HEkScsu9YqMAcG4CfFjs3rj5pVU1w==", "dependencies": { "@discoveryjs/json-ext": "0.5.7", "acorn": "^8.0.4", "acorn-walk": "^8.0.0", - "chalk": "^4.1.0", "commander": "^7.2.0", + "escape-string-regexp": "^4.0.0", "gzip-size": "^6.0.0", - "lodash": "^4.17.20", + "is-plain-object": "^5.0.0", + "lodash.debounce": "^4.0.8", + "lodash.escape": "^4.0.1", + "lodash.flatten": "^4.4.0", + "lodash.invokemap": "^4.6.0", + "lodash.pullall": "^4.2.0", + "lodash.uniqby": "^4.7.0", "opener": "^1.5.2", - "sirv": "^1.0.7", + "picocolors": "^1.0.0", + "sirv": "^2.0.3", "ws": "^7.3.1" }, "bin": { @@ -48066,51 +47594,6 @@ "node": ">=0.4.0" } }, - "node_modules/webpack-bundle-analyzer/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/webpack-bundle-analyzer/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/webpack-bundle-analyzer/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/webpack-bundle-analyzer/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, "node_modules/webpack-bundle-analyzer/node_modules/commander": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", @@ -48119,23 +47602,15 @@ "node": ">= 10" } }, - "node_modules/webpack-bundle-analyzer/node_modules/has-flag": { + "node_modules/webpack-bundle-analyzer/node_modules/escape-string-regexp": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "engines": { - "node": ">=8" - } - }, - "node_modules/webpack-bundle-analyzer/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" + "node": ">=10" }, - "engines": { - "node": ">=8" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/webpack-bundle-analyzer/node_modules/ws": { @@ -48700,6 +48175,14 @@ "mkdirp": "bin/cmd.js" } }, + "node_modules/webpack-dev-server/node_modules/node-forge": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", + "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==", + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/webpack-dev-server/node_modules/p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -48808,6 +48291,14 @@ "node": ">= 4" } }, + "node_modules/webpack-dev-server/node_modules/selfsigned": { + "version": "1.10.14", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.14.tgz", + "integrity": "sha512-lkjaiAye+wBZDCBsu5BGi0XiLRxeUlsGod5ZP924CRSEoGuZAw/f7y9RKu28rwTfiHVhdavhB0qH0INV6P1lEA==", + "dependencies": { + "node-forge": "^0.10.0" + } + }, "node_modules/webpack-dev-server/node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -49209,9 +48700,9 @@ } }, "node_modules/whatwg-fetch": { - "version": "3.6.17", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.17.tgz", - "integrity": "sha512-c4ghIvG6th0eudYwKZY5keb81wtFz9/WeAHAoy8+r18kcWlitUIrmGFQ2rWEl4UCKUilD3zCLHOIPheHx5ypRQ==" + "version": "3.6.18", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.18.tgz", + "integrity": "sha512-ltN7j66EneWn5TFDO4L9inYC1D+Czsxlrw2SalgjMmEMkLfA5SIZxEFdE6QtHFiiM6Q7WL32c7AkI3w6yxM84Q==" }, "node_modules/whatwg-mimetype": { "version": "3.0.0", @@ -49278,15 +48769,15 @@ "license": "ISC" }, "node_modules/which-typed-array": { - "version": "1.1.9", - "license": "MIT", + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.11.tgz", + "integrity": "sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==", "dependencies": { "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.2", "for-each": "^0.3.3", "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0", - "is-typed-array": "^1.1.10" + "has-tostringtag": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -49880,7 +49371,7 @@ }, "node_modules/yargs": { "version": "17.6.2", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "cliui": "^8.0.1", @@ -49897,7 +49388,7 @@ }, "node_modules/yargs-parser": { "version": "21.1.1", - "devOptional": true, + "dev": true, "license": "ISC", "engines": { "node": ">=12" @@ -49905,7 +49396,7 @@ }, "node_modules/yargs/node_modules/cliui": { "version": "8.0.1", - "devOptional": true, + "dev": true, "license": "ISC", "dependencies": { "string-width": "^4.2.0", @@ -50965,9 +50456,9 @@ } }, "@babel/standalone": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/standalone/-/standalone-7.22.10.tgz", - "integrity": "sha512-VmK2sWxUTfDDh9mPfCtFJPIehZToteqK+Zpwq8oJUjJ+WeeKIFTTQIrDzH7jEdom+cAaaguU7FI/FBsBWFkIeQ==" + "version": "7.22.14", + "resolved": "https://registry.npmjs.org/@babel/standalone/-/standalone-7.22.14.tgz", + "integrity": "sha512-i61lDNe0nRm44nZj05g+1HNb0EVfDGaTI6PkoeUMUSel3GTG6T1OM3BdjZIXghnnFB8bSWbmfS1lkBQgNUdu5w==" }, "@babel/template": { "version": "7.18.10", @@ -51473,48 +50964,39 @@ "requires": {} }, "@datadog/browser-core": { - "version": "4.47.0", - "resolved": "https://registry.npmjs.org/@datadog/browser-core/-/browser-core-4.47.0.tgz", - "integrity": "sha512-C3qm4kiT8OoK09UnAed2HTY9ecDaz0n7Qm0m4WwC+lpwOR97oduWdggbvgJgLGdJLleQjqEFHyuB8BEvEQ66BQ==" + "version": "4.48.0", + "resolved": "https://registry.npmjs.org/@datadog/browser-core/-/browser-core-4.48.0.tgz", + "integrity": "sha512-Cs/mGnE+FV3cvJ4Zl9FMuZOltp2Gy4Ji4v1zgodp4XDxU/PLFKppzRLqsfE6NMBgUpQKK9mh9zk1tBZtpUVEjw==" }, "@datadog/browser-logs": { - "version": "4.47.0", - "resolved": "https://registry.npmjs.org/@datadog/browser-logs/-/browser-logs-4.47.0.tgz", - "integrity": "sha512-leDi/qsQVLzWuVu1N1lSNs4mTb9CZ7/p8IMAO2WEg9yIHqTVUZG/+uxWyzbFdexpRo8KuKIBL1hvHQP4BAdwWQ==", + "version": "4.48.0", + "resolved": "https://registry.npmjs.org/@datadog/browser-logs/-/browser-logs-4.48.0.tgz", + "integrity": "sha512-Kha0lNj9rbooG1mX/gsktLrTRYzz/8WC0F0UqfO43MKvOREDgSkqWYAhR2A+91+y+Mvrm2UqwwD3Kv0ZRAGBzw==", "requires": { - "@datadog/browser-core": "4.47.0" + "@datadog/browser-core": "4.48.0" } }, "@datadog/browser-rum": { - "version": "4.47.0", - "resolved": "https://registry.npmjs.org/@datadog/browser-rum/-/browser-rum-4.47.0.tgz", - "integrity": "sha512-+CK8Z7w0fZnCN1oK7S3TQq9LfDmpXDnYxzmHw+11INHHdFhWqmUojpKKi+7+XLMaTOvU83yYBfOBFz2DSWvwew==", + "version": "4.48.0", + "resolved": "https://registry.npmjs.org/@datadog/browser-rum/-/browser-rum-4.48.0.tgz", + "integrity": "sha512-drN+r8JV2EUG5PFUPKJYIpKP7t/hjJmt8IrxfEZyzDErD0Z7imKpKaprjGeottR2XqHngm1+JDXgZTvHPqSNQg==", "requires": { - "@datadog/browser-core": "4.47.0", - "@datadog/browser-rum-core": "4.47.0", - "@datadog/browser-worker": "4.47.0" + "@datadog/browser-core": "4.48.0", + "@datadog/browser-rum-core": "4.48.0" } }, "@datadog/browser-rum-core": { - "version": "4.47.0", - "resolved": "https://registry.npmjs.org/@datadog/browser-rum-core/-/browser-rum-core-4.47.0.tgz", - "integrity": "sha512-6nOnFPZJ0cYkWV4w7lbtCxVGLiquxw68D1qvwXbMtxZ1q+zroLhPY+lpk7Hd6XN7OT7xmTP2wbTnr0DL5VGwig==", - "requires": { - "@datadog/browser-core": "4.47.0" - } - }, - "@datadog/browser-worker": { - "version": "4.47.0", - "resolved": "https://registry.npmjs.org/@datadog/browser-worker/-/browser-worker-4.47.0.tgz", - "integrity": "sha512-7/jiPXiGYStd40zsQl0U6DBkkoKhFPuWgl5R/k4sKaMdZ3YXwhL3M+js7S7MIGsrNvpoZygEQml+McVYZ2Vmyg==", + "version": "4.48.0", + "resolved": "https://registry.npmjs.org/@datadog/browser-rum-core/-/browser-rum-core-4.48.0.tgz", + "integrity": "sha512-qrK55AcBQDzHcq/jHLlz58UeX7qEl/UZU4BVGt226KAhJrn+WQrtk0yzwnSTp9YjamxY0QP62Zu7mP7fAf7Nog==", "requires": { - "@datadog/browser-core": "4.47.0" + "@datadog/browser-core": "4.48.0" } }, "@deriv/api-types": { - "version": "1.0.118", - "resolved": "https://registry.npmjs.org/@deriv/api-types/-/api-types-1.0.118.tgz", - "integrity": "sha512-+q/PEH/O3wRrJlDQWl8x03JCBhQQBZt343maDqDRAM//H2K7h4YT5ucnYHJiS+6xu5jGGeq7JR8lT4R14mfd3Q==" + "version": "1.0.121", + "resolved": "https://registry.npmjs.org/@deriv/api-types/-/api-types-1.0.121.tgz", + "integrity": "sha512-Sj42zIOj8Dbxe3nMN+Qf+tohlsKGRD6dw2lgzd835MDi9G/TZzVTsXJvzlJqvKSPSA94hIBWFc0PBOF6d0V+0g==" }, "@deriv/deriv-api": { "version": "1.0.13", @@ -51687,9 +51169,9 @@ }, "dependencies": { "@babel/runtime": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.10.tgz", - "integrity": "sha512-21t/fkKLMZI4pqP2wlmsQAWnYW1PDyKyyUV4vCi+B25ydmdaYTKXPwCj0BzSUnZf4seIiYvSA3jcZ3gdsMFkLQ==", + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.11.tgz", + "integrity": "sha512-ee7jVNlWN09+KftVOu9n7S8gQzD/Z6hN/I8VBRXW4P1+Xe7kJGXMwu8vds4aGIMHZnNbdpSWCfZZtinytpcAvA==", "requires": { "regenerator-runtime": "^0.14.0" } @@ -51927,11 +51409,11 @@ } }, "@floating-ui/react-dom": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.1.tgz", - "integrity": "sha512-rZtAmSht4Lry6gdhAJDrCp/6rKN7++JnL1/Anbr/DdeyYXQPxvg/ivrbYvJulbRf4vL8b212suwMM2lxbv+RQA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.2.tgz", + "integrity": "sha512-5qhlDvjaLmAst/rKb3VdlCinwTF4EYMiVxuuc/HVUjs46W0zgtbMmAZ1UTsDrRTxRmUEzl92mOtWbeeXL26lSQ==", "requires": { - "@floating-ui/dom": "^1.3.0" + "@floating-ui/dom": "^1.5.1" } }, "@floating-ui/utils": { @@ -54726,6 +54208,17 @@ "esquery": "^1.0.1" } }, + "@playwright/test": { + "version": "1.37.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.37.1.tgz", + "integrity": "sha512-bq9zTli3vWJo8S3LwB91U0qDNQDpEXnw7knhxLM0nwDvexQAwx9tO8iKDZSqqneVq+URd/WIoz+BALMqUTgdSg==", + "dev": true, + "requires": { + "@types/node": "*", + "fsevents": "2.3.2", + "playwright-core": "1.37.1" + } + }, "@pmmmwh/react-refresh-webpack-plugin": { "version": "0.5.11", "resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.11.tgz", @@ -55184,60 +54677,60 @@ "requires": {} }, "@sentry-internal/tracing": { - "version": "7.64.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.64.0.tgz", - "integrity": "sha512-1XE8W6ki7hHyBvX9hfirnGkKDBKNq3bDJyXS86E0bYVDl94nvbRM9BD9DHsCFetqYkVm1yDGEK+6aUVs4CztoQ==", + "version": "7.66.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.66.0.tgz", + "integrity": "sha512-3vCgC2hC3T45pn53yTDVcRpHoJTBxelDPPZVsipAbZnoOVPkj7n6dNfDhj3I3kwWCBPahPkXmE+R4xViR8VqJg==", "requires": { - "@sentry/core": "7.64.0", - "@sentry/types": "7.64.0", - "@sentry/utils": "7.64.0", + "@sentry/core": "7.66.0", + "@sentry/types": "7.66.0", + "@sentry/utils": "7.66.0", "tslib": "^2.4.1 || ^1.9.3" } }, "@sentry/browser": { - "version": "7.64.0", - "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-7.64.0.tgz", - "integrity": "sha512-lB2IWUkZavEDclxfLBp554dY10ZNIEvlDZUWWathW+Ws2wRb6PNLtuPUNu12R7Q7z0xpkOLrM1kRNN0OdldgKA==", - "requires": { - "@sentry-internal/tracing": "7.64.0", - "@sentry/core": "7.64.0", - "@sentry/replay": "7.64.0", - "@sentry/types": "7.64.0", - "@sentry/utils": "7.64.0", + "version": "7.66.0", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-7.66.0.tgz", + "integrity": "sha512-rW037rf8jkhyykG38+HUdwkRCKHJEMM5NkCqPIO5zuuxfLKukKdI2rbvgJ93s3/9UfsTuDFcKFL1u43mCn6sDw==", + "requires": { + "@sentry-internal/tracing": "7.66.0", + "@sentry/core": "7.66.0", + "@sentry/replay": "7.66.0", + "@sentry/types": "7.66.0", + "@sentry/utils": "7.66.0", "tslib": "^2.4.1 || ^1.9.3" } }, "@sentry/core": { - "version": "7.64.0", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.64.0.tgz", - "integrity": "sha512-IzmEyl5sNG7NyEFiyFHEHC+sizsZp9MEw1+RJRLX6U5RITvcsEgcajSkHQFafaBPzRrcxZMdm47Cwhl212LXcw==", + "version": "7.66.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.66.0.tgz", + "integrity": "sha512-WMAEPN86NeCJ1IT48Lqiz4MS5gdDjBwP4M63XP4msZn9aujSf2Qb6My5uT87AJr9zBtgk8MyJsuHr35F0P3q1w==", "requires": { - "@sentry/types": "7.64.0", - "@sentry/utils": "7.64.0", + "@sentry/types": "7.66.0", + "@sentry/utils": "7.66.0", "tslib": "^2.4.1 || ^1.9.3" } }, "@sentry/replay": { - "version": "7.64.0", - "resolved": "https://registry.npmjs.org/@sentry/replay/-/replay-7.64.0.tgz", - "integrity": "sha512-alaMCZDZhaAVmEyiUnszZnvfdbiZx5MmtMTGrlDd7tYq3K5OA9prdLqqlmfIJYBfYtXF3lD0iZFphOZQD+4CIw==", + "version": "7.66.0", + "resolved": "https://registry.npmjs.org/@sentry/replay/-/replay-7.66.0.tgz", + "integrity": "sha512-5Y2SlVTOFTo3uIycv0mRneBakQtLgWkOnsJaC5LB0Ip0TqVKiMCbQ578vvXp+yvRj4LcS1gNd98xTTNojBoQNg==", "requires": { - "@sentry/core": "7.64.0", - "@sentry/types": "7.64.0", - "@sentry/utils": "7.64.0" + "@sentry/core": "7.66.0", + "@sentry/types": "7.66.0", + "@sentry/utils": "7.66.0" } }, "@sentry/types": { - "version": "7.64.0", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.64.0.tgz", - "integrity": "sha512-LqjQprWXjUFRmzIlUjyA+KL+38elgIYmAeoDrdyNVh8MK5IC1W2Lh1Q87b4yOiZeMiIhIVNBd7Ecoh2rodGrGA==" + "version": "7.66.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.66.0.tgz", + "integrity": "sha512-uUMSoSiar6JhuD8p7ON/Ddp4JYvrVd2RpwXJRPH1A4H4Bd4DVt1mKJy1OLG6HdeQv39XyhB1lPZckKJg4tATPw==" }, "@sentry/utils": { - "version": "7.64.0", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.64.0.tgz", - "integrity": "sha512-HRlM1INzK66Gt+F4vCItiwGKAng4gqzCR4C5marsL3qv6SrKH98dQnCGYgXluSWaaa56h97FRQu7TxCk6jkSvQ==", + "version": "7.66.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.66.0.tgz", + "integrity": "sha512-9GYUVgXjK66uXXcLXVMXVzlptqMtq1eJENCuDeezQiEFrNA71KkLDg00wESp+LL+bl3wpVTBApArpbF6UEG5hQ==", "requires": { - "@sentry/types": "7.64.0", + "@sentry/types": "7.66.0", "tslib": "^2.4.1 || ^1.9.3" } }, @@ -56210,9 +55703,9 @@ "integrity": "sha512-h4lTMgMJctJybDp8CQrxTUiiYmedihHWkjnF/8Pxseu2S6Nlfcy8kwboQ8yejh456rP2yWoEVm1sS/FVsfM48w==" }, "@types/node": { - "version": "16.18.44", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.44.tgz", - "integrity": "sha512-PZXtT+wqSMHnLPVExTh+tMt1VK+GvjRLsGZMbcQ4Mb/cG63xJig/TUmgrDa9aborl2i22UnpIzHYNu7s97NbBQ==" + "version": "16.18.46", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.46.tgz", + "integrity": "sha512-Mnq3O9Xz52exs3mlxMcQuA7/9VFe/dXcrgAyfjLkABIqxXKOgBRjyazTxUbjsxDa4BP7hhPliyjVTP9RDP14xg==" }, "@webassemblyjs/ast": { "version": "1.9.0", @@ -57233,9 +56726,9 @@ }, "dependencies": { "@types/node": { - "version": "16.18.44", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.44.tgz", - "integrity": "sha512-PZXtT+wqSMHnLPVExTh+tMt1VK+GvjRLsGZMbcQ4Mb/cG63xJig/TUmgrDa9aborl2i22UnpIzHYNu7s97NbBQ==" + "version": "16.18.46", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.46.tgz", + "integrity": "sha512-Mnq3O9Xz52exs3mlxMcQuA7/9VFe/dXcrgAyfjLkABIqxXKOgBRjyazTxUbjsxDa4BP7hhPliyjVTP9RDP14xg==" }, "colorette": { "version": "1.4.0", @@ -57531,9 +57024,9 @@ } }, "@types/node": { - "version": "16.18.44", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.44.tgz", - "integrity": "sha512-PZXtT+wqSMHnLPVExTh+tMt1VK+GvjRLsGZMbcQ4Mb/cG63xJig/TUmgrDa9aborl2i22UnpIzHYNu7s97NbBQ==" + "version": "16.18.46", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.46.tgz", + "integrity": "sha512-Mnq3O9Xz52exs3mlxMcQuA7/9VFe/dXcrgAyfjLkABIqxXKOgBRjyazTxUbjsxDa4BP7hhPliyjVTP9RDP14xg==" }, "@webassemblyjs/ast": { "version": "1.9.0", @@ -58206,9 +57699,9 @@ }, "dependencies": { "@types/node": { - "version": "16.18.44", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.44.tgz", - "integrity": "sha512-PZXtT+wqSMHnLPVExTh+tMt1VK+GvjRLsGZMbcQ4Mb/cG63xJig/TUmgrDa9aborl2i22UnpIzHYNu7s97NbBQ==" + "version": "16.18.46", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.46.tgz", + "integrity": "sha512-Mnq3O9Xz52exs3mlxMcQuA7/9VFe/dXcrgAyfjLkABIqxXKOgBRjyazTxUbjsxDa4BP7hhPliyjVTP9RDP14xg==" }, "@webassemblyjs/ast": { "version": "1.9.0", @@ -58963,9 +58456,9 @@ "integrity": "sha512-h4lTMgMJctJybDp8CQrxTUiiYmedihHWkjnF/8Pxseu2S6Nlfcy8kwboQ8yejh456rP2yWoEVm1sS/FVsfM48w==" }, "@types/node": { - "version": "16.18.44", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.44.tgz", - "integrity": "sha512-PZXtT+wqSMHnLPVExTh+tMt1VK+GvjRLsGZMbcQ4Mb/cG63xJig/TUmgrDa9aborl2i22UnpIzHYNu7s97NbBQ==" + "version": "16.18.46", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.46.tgz", + "integrity": "sha512-Mnq3O9Xz52exs3mlxMcQuA7/9VFe/dXcrgAyfjLkABIqxXKOgBRjyazTxUbjsxDa4BP7hhPliyjVTP9RDP14xg==" }, "@webassemblyjs/ast": { "version": "1.9.0", @@ -59975,9 +59468,9 @@ }, "dependencies": { "@types/node": { - "version": "16.18.44", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.44.tgz", - "integrity": "sha512-PZXtT+wqSMHnLPVExTh+tMt1VK+GvjRLsGZMbcQ4Mb/cG63xJig/TUmgrDa9aborl2i22UnpIzHYNu7s97NbBQ==" + "version": "16.18.46", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.46.tgz", + "integrity": "sha512-Mnq3O9Xz52exs3mlxMcQuA7/9VFe/dXcrgAyfjLkABIqxXKOgBRjyazTxUbjsxDa4BP7hhPliyjVTP9RDP14xg==" }, "ansi-styles": { "version": "4.3.0", @@ -60308,9 +59801,9 @@ "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==" }, "@types/node": { - "version": "16.18.44", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.44.tgz", - "integrity": "sha512-PZXtT+wqSMHnLPVExTh+tMt1VK+GvjRLsGZMbcQ4Mb/cG63xJig/TUmgrDa9aborl2i22UnpIzHYNu7s97NbBQ==" + "version": "16.18.46", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.46.tgz", + "integrity": "sha512-Mnq3O9Xz52exs3mlxMcQuA7/9VFe/dXcrgAyfjLkABIqxXKOgBRjyazTxUbjsxDa4BP7hhPliyjVTP9RDP14xg==" }, "acorn": { "version": "7.4.1", @@ -61090,15 +60583,23 @@ "version": "0.0.29" }, "@types/loadjs": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@types/loadjs/-/loadjs-4.0.1.tgz", - "integrity": "sha512-UEMOleWwITwDvj+kI6T4etC9yMjmejH6UdJRAa1MWZwzDIW+Iz7T6z6Zc96N/5FeFsxAEBA/zP1ki+HluXPcHQ==" + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/loadjs/-/loadjs-4.0.2.tgz", + "integrity": "sha512-tsPs2Pv+T+MYU6wh+a5IZS9ryHrQza27j+yJ84yiPCLQgDjjM7aBfjAKPgDi9ks1x5vY0tESvux/SwHRAlMqLQ==" }, "@types/lodash": { "version": "4.14.197", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.197.tgz", "integrity": "sha512-BMVOiWs0uNxHVlHBgzTIqJYmj+PgCo4euloGF+5m4okL3rEYzM2EEv78mw8zWSMM57dM7kVIgJ2QDvwHSoCI5g==" }, + "@types/lodash.debounce": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@types/lodash.debounce/-/lodash.debounce-4.0.7.tgz", + "integrity": "sha512-X1T4wMZ+gT000M2/91SYj0d/7JfeNZ9PeeOldSNoE/lunLeQXKvkmIumI29IaKMotU/ln/McOIvgzZcQ/3TrSA==", + "requires": { + "@types/lodash": "*" + } + }, "@types/lodash.merge": { "version": "4.6.7", "resolved": "https://registry.npmjs.org/@types/lodash.merge/-/lodash.merge-4.6.7.tgz", @@ -61131,7 +60632,7 @@ }, "@types/minimist": { "version": "1.2.2", - "devOptional": true + "dev": true }, "@types/node": { "version": "17.0.45" @@ -61184,22 +60685,22 @@ "version": "15.7.5" }, "@types/q": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.5.tgz", - "integrity": "sha512-L28j2FcJfSZOnL1WBjDYp2vUHCeIFlyYI/53EwD/rKUBQ7MtUUfbQWiyKJGpcnv4/WgrhWsFKrcPstcAt/J0tQ==" + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.6.tgz", + "integrity": "sha512-IKjZ8RjTSwD4/YG+2gtj7BPFRB/lNbWKTiSj3M7U/TD2B7HfYCxvp2Zz6xA2WIY7pAuL1QOUPw8gQRbUrrq4fQ==" }, "@types/qrcode.react": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@types/qrcode.react/-/qrcode.react-1.0.2.tgz", - "integrity": "sha512-I9Oq5Cjlkgy3Tw7krCnCXLw2/zMhizkTere49OOcta23tkvH0xBTP0yInimTh0gstLRtb8Ki9NZVujE5UI6ffQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/qrcode.react/-/qrcode.react-1.0.3.tgz", + "integrity": "sha512-gl7Ozf3BRQwfDUAU2xx7sWRBe/s7TqO0HAJukSQHbEVfQrFo5WKgZl0BHlN8u9W1DHXb4elgKRolHLZkgETXyA==", "requires": { "@types/react": "*" } }, "@types/qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" + "version": "6.9.8", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.8.tgz", + "integrity": "sha512-u95svzDlTysU5xecFNTgfFG5RUWu1A9P0VzgpcIiGZA9iraHOdSzcxMxQ55DyeRaGCSxQi7LxXDI4rzq/MYfdg==" }, "@types/reach__router": { "version": "1.3.11", @@ -61358,9 +60859,9 @@ "integrity": "sha512-NfQ4gyz38SL8sDNrSixxU2Os1a5xcdFxipAFxYEuLUlvU2uDwS4NUpsImcf1//SlWItCVMMLiylsxbmNMToV/g==" }, "@types/uglify-js": { - "version": "3.17.1", - "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.17.1.tgz", - "integrity": "sha512-GkewRA4i5oXacU/n4MA9+bLgt5/L3F1mKrYvFGm7r2ouLXhRKjuWwo9XHNnbx6WF3vlGW21S3fCvgqxvxXXc5g==", + "version": "3.17.2", + "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.17.2.tgz", + "integrity": "sha512-9SjrHO54LINgC/6Ehr81NjAxAYvwEZqjUHLjJYvC4Nmr9jbLQCIZbWSvl4vXQkkmR1UAuaKDycau3O1kWGFyXQ==", "requires": { "source-map": "^0.6.1" } @@ -61413,6 +60914,14 @@ } } }, + "@types/ws": { + "version": "8.5.5", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.5.tgz", + "integrity": "sha512-lwhs8hktwxSjf9UaZ9tG5M03PGogvFaH8gUgLNbN9HKIg0dvv6q+gkSuJ8HN4/VbyxkuLzCjlN7GquQ0gUJfIg==", + "requires": { + "@types/node": "*" + } + }, "@types/yargs": { "version": "15.0.14", "requires": { @@ -61962,7 +61471,7 @@ }, "abbrev": { "version": "1.1.1", - "devOptional": true + "dev": true }, "accepts": { "version": "1.3.8", @@ -62044,7 +61553,7 @@ }, "agentkeepalive": { "version": "4.2.1", - "devOptional": true, + "dev": true, "requires": { "debug": "^4.1.0", "depd": "^1.1.2", @@ -62186,7 +61695,7 @@ }, "are-we-there-yet": { "version": "3.0.1", - "devOptional": true, + "dev": true, "requires": { "delegates": "^1.0.0", "readable-stream": "^3.6.0" @@ -62318,13 +61827,13 @@ } }, "array.prototype.reduce": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/array.prototype.reduce/-/array.prototype.reduce-1.0.5.tgz", - "integrity": "sha512-kDdugMl7id9COE8R7MHF5jWk7Dqt/fs4Pv+JXoICnYwqpjjjbUurz6w5fT5IG6brLdJhv6/VoHB0H7oyIBXd+Q==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/array.prototype.reduce/-/array.prototype.reduce-1.0.6.tgz", + "integrity": "sha512-UW+Mz8LG/sPSU8jRDCjVr6J/ZKAGpHfwrZ6kWTG5qCxIEiXdVshqGnu5vEZA8S1y6X4aCSbQZ0/EEsfvEvBiSg==", "requires": { "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", "es-array-method-boxes-properly": "^1.0.0", "is-string": "^1.0.7" } @@ -62339,9 +61848,22 @@ "get-intrinsic": "^1.1.3" } }, + "arraybuffer.prototype.slice": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.1.tgz", + "integrity": "sha512-09x0ZWFEjj4WD8PDbykUwo3t9arLn8NIzmmYEJFpYekOAQjpkGSyrQhNoRTcwwcFRu+ycWF78QZ63oWTqSjBcw==", + "requires": { + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "get-intrinsic": "^1.2.1", + "is-array-buffer": "^3.0.2", + "is-shared-array-buffer": "^1.0.2" + } + }, "arrify": { "version": "1.0.1", - "devOptional": true + "dev": true }, "asap": { "version": "2.0.6" @@ -62429,13 +61951,6 @@ "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.6.tgz", "integrity": "sha512-c646jH1avxr+aVpndVMeAfYw7wAa6idufrlN3LPA4PmKS0QEGp6PIC9nwz0WQkkvBGAMEki3pFdtxaF39J9vvg==" }, - "async-foreach": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/async-foreach/-/async-foreach-0.1.3.tgz", - "integrity": "sha512-VUeSMD8nEGBWaZK4lizI1sf3yEC7pnAQ/mrI7pC2fBz2s/tq5jWWEngTwaf0Gruu/OoXRGLGg1XFqpYBiGTYJA==", - "optional": true, - "peer": true - }, "async-limiter": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", @@ -64020,7 +63535,7 @@ }, "camelcase-keys": { "version": "6.2.2", - "devOptional": true, + "dev": true, "requires": { "camelcase": "^5.3.1", "map-obj": "^4.0.0", @@ -64075,9 +63590,9 @@ "integrity": "sha512-vlNK021QdI7PNeiUh/lKkC/mNHHfV0m/Ad5JoI0TYtlBnJAslM/JIkm/tGC88bkLIwO6OQ5uV6ztS6kVAtCDlg==" }, "chai": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz", - "integrity": "sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==", + "version": "4.3.8", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.8.tgz", + "integrity": "sha512-vX4YvVVtxlfSZ2VecZgFUTU5qPCYsobVI2O9FmwEXBhDigYGQA6jRXCycIs1yJnnWbZ6/+a2zNIF5DfVCcJBFQ==", "peer": true, "requires": { "assertion-error": "^1.1.0", @@ -66003,7 +65518,7 @@ }, "decamelize-keys": { "version": "1.1.1", - "devOptional": true, + "dev": true, "requires": { "decamelize": "^1.1.0", "map-obj": "^1.0.0" @@ -66011,7 +65526,7 @@ "dependencies": { "map-obj": { "version": "1.0.1", - "devOptional": true + "dev": true } } }, @@ -67035,14 +66550,14 @@ }, "env-paths": { "version": "2.2.1", - "devOptional": true + "dev": true }, "envinfo": { "version": "7.8.1" }, "err-code": { "version": "2.0.3", - "devOptional": true + "dev": true }, "errno": { "version": "0.1.8", @@ -67067,17 +66582,18 @@ } }, "es-abstract": { - "version": "1.21.2", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.2.tgz", - "integrity": "sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==", + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.1.tgz", + "integrity": "sha512-ioRRcXMO6OFyRpyzV3kE1IIBd4WG5/kltnzdxSCqoP8CMGs/Li+M1uF5o7lOkZVFjDs+NLesthnF66Pg/0q0Lw==", "requires": { "array-buffer-byte-length": "^1.0.0", + "arraybuffer.prototype.slice": "^1.0.1", "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.2", "es-set-tostringtag": "^2.0.1", "es-to-primitive": "^1.2.1", "function.prototype.name": "^1.1.5", - "get-intrinsic": "^1.2.0", + "get-intrinsic": "^1.2.1", "get-symbol-description": "^1.0.0", "globalthis": "^1.0.3", "gopd": "^1.0.1", @@ -67097,14 +66613,18 @@ "object-inspect": "^1.12.3", "object-keys": "^1.1.1", "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.4.3", + "regexp.prototype.flags": "^1.5.0", + "safe-array-concat": "^1.0.0", "safe-regex-test": "^1.0.0", "string.prototype.trim": "^1.2.7", "string.prototype.trimend": "^1.0.6", "string.prototype.trimstart": "^1.0.6", + "typed-array-buffer": "^1.0.0", + "typed-array-byte-length": "^1.0.0", + "typed-array-byte-offset": "^1.0.0", "typed-array-length": "^1.0.4", "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.9" + "which-typed-array": "^1.1.10" } }, "es-array-method-boxes-properly": { @@ -69142,9 +68662,9 @@ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" }, "fraction.js": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.1.tgz", - "integrity": "sha512-/KxoyCnPM0GwYI4NN0Iag38Tqt+od3/mLuguepLgCAKPn0ZhC544nssAW0tG2/00zXEYl9W+7hwAIpLHo6Oc7Q==" + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.4.tgz", + "integrity": "sha512-pwiTgt0Q7t+GHZA4yaLjObx4vXmmdcS0iSJ19o8d/goUGgItX9UZWKWNnLHehxviD8wU2IWRsnR8cD5+yOJP2Q==" }, "fragment-cache": { "version": "0.2.1", @@ -69320,7 +68840,7 @@ }, "gauge": { "version": "4.0.4", - "devOptional": true, + "dev": true, "requires": { "aproba": "^1.0.3 || ^2.0.0", "color-support": "^1.1.3", @@ -69332,16 +68852,6 @@ "wide-align": "^1.1.5" } }, - "gaze": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz", - "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==", - "optional": true, - "peer": true, - "requires": { - "globule": "^1.0.0" - } - }, "gensync": { "version": "1.0.0-beta.2" }, @@ -69355,12 +68865,13 @@ "peer": true }, "get-intrinsic": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", - "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", + "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", "requires": { "function-bind": "^1.1.1", "has": "^1.0.3", + "has-proto": "^1.0.1", "has-symbols": "^1.0.3" } }, @@ -69706,45 +69217,6 @@ "version": "0.1.4", "dev": true }, - "globule": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/globule/-/globule-1.3.4.tgz", - "integrity": "sha512-OPTIfhMBh7JbBYDpa5b+Q5ptmMWKwcNcFSR/0c6t8V4f3ZAVBEsKNY37QdVqmLRYSMhOUGYrY0QhSoEpzGr/Eg==", - "optional": true, - "peer": true, - "requires": { - "glob": "~7.1.1", - "lodash": "^4.17.21", - "minimatch": "~3.0.2" - }, - "dependencies": { - "glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", - "optional": true, - "peer": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "minimatch": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz", - "integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==", - "optional": true, - "peer": true, - "requires": { - "brace-expansion": "^1.1.7" - } - } - } - }, "gonzales-pe": { "version": "4.3.0", "dev": true, @@ -69893,7 +69365,7 @@ }, "hard-rejection": { "version": "2.1.0", - "devOptional": true + "dev": true }, "has": { "version": "1.0.3", @@ -70154,14 +69626,14 @@ }, "hosted-git-info": { "version": "4.1.0", - "devOptional": true, + "dev": true, "requires": { "lru-cache": "^6.0.0" }, "dependencies": { "lru-cache": { "version": "6.0.0", - "devOptional": true, + "dev": true, "requires": { "yallist": "^4.0.0" } @@ -70446,7 +69918,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", - "devOptional": true + "dev": true }, "http-deceiver": { "version": "1.2.7", @@ -70642,7 +70114,7 @@ }, "humanize-ms": { "version": "1.2.1", - "devOptional": true, + "dev": true, "requires": { "ms": "^2.0.0" } @@ -71161,7 +70633,7 @@ }, "is-lambda": { "version": "1.0.1", - "devOptional": true + "dev": true }, "is-lite": { "version": "0.9.3", @@ -73946,12 +73418,27 @@ "lodash.debounce": { "version": "4.0.8" }, + "lodash.escape": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-4.0.1.tgz", + "integrity": "sha512-nXEOnb/jK9g0DYMr1/Xvq6l5xMD7GDG55+GSYIYmS0G4tBk/hURD4JR9WCavs04t33WmJx9kCyp9vJ+mr4BOUw==" + }, + "lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==" + }, "lodash.get": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", "peer": true }, + "lodash.invokemap": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.invokemap/-/lodash.invokemap-4.6.0.tgz", + "integrity": "sha512-CfkycNtMqgUlfjfdh2BhKO/ZXrP8ePOX5lEU/g0R3ItJcnuxWDwokMGKx1hWcfOikmyOVx6X9IwWnDGlgKl61w==" + }, "lodash.isfunction": { "version": "3.0.9", "dev": true @@ -73979,6 +73466,11 @@ "version": "4.6.2", "dev": true }, + "lodash.pullall": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.pullall/-/lodash.pullall-4.2.0.tgz", + "integrity": "sha512-VhqxBKH0ZxPpLhiu68YD1KnHmbhQJQctcipvmFnqIBDYzcIHzf3Zpu0tpeOKtR4x76p9yohc506eGdOjTmyIBg==" + }, "lodash.snakecase": { "version": "4.1.1", "dev": true @@ -74003,6 +73495,11 @@ "lodash.uniq": { "version": "4.5.0" }, + "lodash.uniqby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz", + "integrity": "sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww==" + }, "lodash.upperfirst": { "version": "4.3.1", "dev": true @@ -74224,7 +73721,7 @@ }, "map-obj": { "version": "4.3.0", - "devOptional": true + "dev": true }, "map-or-similar": { "version": "1.5.0", @@ -74582,7 +74079,7 @@ }, "minimist-options": { "version": "4.1.0", - "devOptional": true, + "dev": true, "requires": { "arrify": "^1.0.1", "is-plain-obj": "^1.1.0", @@ -74633,7 +74130,7 @@ }, "minipass-sized": { "version": "1.0.3", - "devOptional": true, + "dev": true, "requires": { "minipass": "^3.0.0" } @@ -74757,9 +74254,9 @@ } }, "mobx": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/mobx/-/mobx-6.10.0.tgz", - "integrity": "sha512-WMbVpCMFtolbB8swQ5E2YRrU+Yu8iLozCVx3CdGjbBKlP7dFiCSuiG06uea3JCFN5DnvtAX7+G5Bp82e2xu0ww==" + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/mobx/-/mobx-6.10.2.tgz", + "integrity": "sha512-B1UGC3ieK3boCjnMEcZSwxqRDMdzX65H/8zOHbuTY8ZhvrIjTUoLRR2TP2bPqIgYRfb3+dUigu8yMZufNjn0LQ==" }, "mobx-persist-store": { "version": "1.1.2", @@ -75059,9 +74556,9 @@ } }, "node-forge": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", - "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==" + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==" }, "node-gyp": { "version": "9.3.0", @@ -75233,385 +74730,16 @@ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==" }, - "node-sass": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-7.0.3.tgz", - "integrity": "sha512-8MIlsY/4dXUkJDYht9pIWBhMil3uHmE8b/AdJPjmFn1nBx9X9BASzfzmsCy0uCCb8eqI3SYYzVPDswWqSx7gjw==", - "optional": true, - "peer": true, - "requires": { - "async-foreach": "^0.1.3", - "chalk": "^4.1.2", - "cross-spawn": "^7.0.3", - "gaze": "^1.0.0", - "get-stdin": "^4.0.1", - "glob": "^7.0.3", - "lodash": "^4.17.15", - "meow": "^9.0.0", - "nan": "^2.13.2", - "node-gyp": "^8.4.1", - "npmlog": "^5.0.0", - "request": "^2.88.0", - "sass-graph": "^4.0.1", - "stdout-stream": "^1.4.0", - "true-case-path": "^1.0.2" - }, - "dependencies": { - "@npmcli/fs": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz", - "integrity": "sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==", - "optional": true, - "peer": true, - "requires": { - "@gar/promisify": "^1.0.1", - "semver": "^7.3.5" - } - }, - "@npmcli/move-file": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", - "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", - "optional": true, - "peer": true, - "requires": { - "mkdirp": "^1.0.4", - "rimraf": "^3.0.2" - } - }, - "@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", - "optional": true, - "peer": true - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "optional": true, - "peer": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "cacache": { - "version": "15.3.0", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz", - "integrity": "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==", - "optional": true, - "peer": true, - "requires": { - "@npmcli/fs": "^1.0.0", - "@npmcli/move-file": "^1.0.1", - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "glob": "^7.1.4", - "infer-owner": "^1.0.4", - "lru-cache": "^6.0.0", - "minipass": "^3.1.1", - "minipass-collect": "^1.0.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.2", - "mkdirp": "^1.0.3", - "p-map": "^4.0.0", - "promise-inflight": "^1.0.1", - "rimraf": "^3.0.2", - "ssri": "^8.0.1", - "tar": "^6.0.2", - "unique-filename": "^1.1.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "optional": true, - "peer": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "optional": true, - "peer": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "optional": true, - "peer": true - }, - "get-stdin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", - "integrity": "sha512-F5aQMywwJ2n85s4hJPTT9RPxGmubonuB10MNYo17/xph174n2MIR33HRguhzVag10O/npM7SPk73LMZNP+FaWw==", - "optional": true, - "peer": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "optional": true, - "peer": true - }, - "http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", - "optional": true, - "peer": true, - "requires": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" - } - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "optional": true, - "peer": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "make-fetch-happen": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz", - "integrity": "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==", - "optional": true, - "peer": true, - "requires": { - "agentkeepalive": "^4.1.3", - "cacache": "^15.2.0", - "http-cache-semantics": "^4.1.0", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", - "is-lambda": "^1.0.1", - "lru-cache": "^6.0.0", - "minipass": "^3.1.3", - "minipass-collect": "^1.0.2", - "minipass-fetch": "^1.3.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.2", - "promise-retry": "^2.0.1", - "socks-proxy-agent": "^6.0.0", - "ssri": "^8.0.0" - } - }, - "meow": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz", - "integrity": "sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==", - "optional": true, - "peer": true, - "requires": { - "@types/minimist": "^1.2.0", - "camelcase-keys": "^6.2.2", - "decamelize": "^1.2.0", - "decamelize-keys": "^1.1.0", - "hard-rejection": "^2.1.0", - "minimist-options": "4.1.0", - "normalize-package-data": "^3.0.0", - "read-pkg-up": "^7.0.1", - "redent": "^3.0.0", - "trim-newlines": "^3.0.0", - "type-fest": "^0.18.0", - "yargs-parser": "^20.2.3" - } - }, - "minipass-fetch": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.4.1.tgz", - "integrity": "sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==", - "optional": true, - "peer": true, - "requires": { - "encoding": "^0.1.12", - "minipass": "^3.1.0", - "minipass-sized": "^1.0.3", - "minizlib": "^2.0.0" - } - }, - "node-gyp": { - "version": "8.4.1", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz", - "integrity": "sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==", - "optional": true, - "peer": true, - "requires": { - "env-paths": "^2.2.0", - "glob": "^7.1.4", - "graceful-fs": "^4.2.6", - "make-fetch-happen": "^9.1.0", - "nopt": "^5.0.0", - "npmlog": "^6.0.0", - "rimraf": "^3.0.2", - "semver": "^7.3.5", - "tar": "^6.1.2", - "which": "^2.0.2" - }, - "dependencies": { - "npmlog": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", - "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", - "optional": true, - "peer": true, - "requires": { - "are-we-there-yet": "^3.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^4.0.3", - "set-blocking": "^2.0.0" - } - } - } - }, - "npmlog": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", - "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", - "optional": true, - "peer": true, - "requires": { - "are-we-there-yet": "^2.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^3.0.0", - "set-blocking": "^2.0.0" - }, - "dependencies": { - "are-we-there-yet": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", - "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", - "optional": true, - "peer": true, - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" - } - }, - "gauge": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", - "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", - "optional": true, - "peer": true, - "requires": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.2", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.1", - "object-assign": "^4.1.1", - "signal-exit": "^3.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.2" - } - } - } - }, - "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "optional": true, - "peer": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "socks-proxy-agent": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz", - "integrity": "sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==", - "optional": true, - "peer": true, - "requires": { - "agent-base": "^6.0.2", - "debug": "^4.3.3", - "socks": "^2.6.2" - } - }, - "ssri": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", - "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", - "optional": true, - "peer": true, - "requires": { - "minipass": "^3.1.1" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "optional": true, - "peer": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "type-fest": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", - "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", - "optional": true, - "peer": true - }, - "unique-filename": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", - "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", - "optional": true, - "peer": true, - "requires": { - "unique-slug": "^2.0.0" - } - }, - "unique-slug": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", - "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", - "optional": true, - "peer": true, - "requires": { - "imurmurhash": "^0.1.4" - } - }, - "yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "optional": true, - "peer": true - } - } - }, "nopt": { "version": "5.0.0", - "devOptional": true, + "dev": true, "requires": { "abbrev": "1" } }, "normalize-package-data": { "version": "3.0.3", - "devOptional": true, + "dev": true, "requires": { "hosted-git-info": "^4.0.1", "is-core-module": "^2.5.0", @@ -75621,14 +74749,14 @@ "dependencies": { "lru-cache": { "version": "6.0.0", - "devOptional": true, + "dev": true, "requires": { "yallist": "^4.0.0" } }, "semver": { "version": "7.3.8", - "devOptional": true, + "dev": true, "requires": { "lru-cache": "^6.0.0" } @@ -77049,6 +76177,12 @@ } } }, + "playwright-core": { + "version": "1.37.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.37.1.tgz", + "integrity": "sha512-17EuQxlSIYCmEMwzMqusJ2ztDgJePjrbttaefgdsiqeLWidjYz9BxXaTaZWxH1J95SHGk6tjE+dwgWILJoUZfA==", + "dev": true + }, "please-upgrade-node": { "version": "3.2.0", "dev": true, @@ -78076,7 +77210,7 @@ }, "promise-retry": { "version": "2.0.1", - "devOptional": true, + "dev": true, "requires": { "err-code": "^2.0.2", "retry": "^0.12.0" @@ -78096,13 +77230,13 @@ } }, "promise.prototype.finally": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/promise.prototype.finally/-/promise.prototype.finally-3.1.4.tgz", - "integrity": "sha512-nNc3YbgMfLzqtqvO/q5DP6RR0SiHI9pUPGzyDf1q+usTwCN2kjvAnJkBb7bHe3o+fFSBPpsGMoYtaSi+LTNqng==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/promise.prototype.finally/-/promise.prototype.finally-3.1.5.tgz", + "integrity": "sha512-4TQ3Dk8yyUZGyU+UXInKdkQ2b6xtiBXAIScGAtGnXVmJtG1uOrxRgbF1ggIu72uzoWFzUfT3nUKa1SuMm9NBdg==", "requires": { "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" } }, "prompts": { @@ -78402,7 +77536,7 @@ }, "quick-lru": { "version": "4.0.1", - "devOptional": true + "dev": true }, "ramda": { "version": "0.28.0", @@ -79384,7 +78518,7 @@ }, "redent": { "version": "3.0.0", - "devOptional": true, + "dev": true, "requires": { "indent-string": "^4.0.0", "strip-indent": "^3.0.0" @@ -79479,11 +78613,13 @@ "integrity": "sha512-jbD/FT0+9MBU2XAZluI7w2OBs1RBi6p9M83nkoZayQXXU9e8Robt69FcZc7wU4eJD/YFTjn1JdCk3rbMJajz8Q==" }, "regexp.prototype.flags": { - "version": "1.4.3", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", + "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==", "requires": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "functions-have-names": "^1.2.2" + "define-properties": "^1.2.0", + "functions-have-names": "^1.2.3" } }, "regexpp": { @@ -80080,9 +79216,9 @@ "version": "4.8.5" }, "rudder-sdk-js": { - "version": "2.40.4", - "resolved": "https://registry.npmjs.org/rudder-sdk-js/-/rudder-sdk-js-2.40.4.tgz", - "integrity": "sha512-IK/XDL4x3LbRDFLb9zgTS+uYyX+ATdhvLjzsWeM6TBPdv9+FkUHzPvgzymDFHEt3x6OKm858LdE6AzBUQeO2yg==" + "version": "2.40.5", + "resolved": "https://registry.npmjs.org/rudder-sdk-js/-/rudder-sdk-js-2.40.5.tgz", + "integrity": "sha512-MGfRz/kocGlTzYYf/HNEfYEDHtKDT9jW4K6I8rcU/SsTeqEhoGXjnlvfeKkwUs9LqbuX+s4AC+AvSfUdBbSGYQ==" }, "run-async": { "version": "2.4.1", @@ -80336,19 +79472,6 @@ } } }, - "sass-graph": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-4.0.1.tgz", - "integrity": "sha512-5YCfmGBmxoIRYHnKK2AKzrAkCoQ8ozO+iumT8K4tXJXRVCPf+7s1/9KxTSW3Rbvf+7Y7b4FR3mWyLnQr3PHocA==", - "optional": true, - "peer": true, - "requires": { - "glob": "^7.0.0", - "lodash": "^4.17.11", - "scss-tokenizer": "^0.4.3", - "yargs": "^17.2.1" - } - }, "sass-loader": { "version": "12.6.0", "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-12.6.0.tgz", @@ -80491,26 +79614,6 @@ "resolved": "https://registry.npmjs.org/scrollparent/-/scrollparent-2.1.0.tgz", "integrity": "sha512-bnnvJL28/Rtz/kz2+4wpBjHzWoEzXhVg/TE8BeVGJHUqE8THNIRnDxDWMktwM+qahvlRdvlLdsQfYe+cuqfZeA==" }, - "scss-tokenizer": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.4.3.tgz", - "integrity": "sha512-raKLgf1LI5QMQnG+RxHz6oK0sL3x3I4FN2UDLqgLOGO8hodECNnNh5BXn7fAyBxrA8zVzdQizQ6XjNJQ+uBwMw==", - "optional": true, - "peer": true, - "requires": { - "js-base64": "^2.4.9", - "source-map": "^0.7.3" - }, - "dependencies": { - "source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", - "optional": true, - "peer": true - } - } - }, "seedrandom": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz", @@ -80528,11 +79631,11 @@ "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==" }, "selfsigned": { - "version": "1.10.14", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.14.tgz", - "integrity": "sha512-lkjaiAye+wBZDCBsu5BGi0XiLRxeUlsGod5ZP924CRSEoGuZAw/f7y9RKu28rwTfiHVhdavhB0qH0INV6P1lEA==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.1.1.tgz", + "integrity": "sha512-GSL3aowiF7wa/WtSFwnUrludWFoNhftq8bUkH9pkzjpN2XSPOAYEgg6e0sS9s0rZwgJzJiQRPU18A6clnoW5wQ==", "requires": { - "node-forge": "^0.10.0" + "node-forge": "^1" } }, "semver": { @@ -80871,13 +79974,13 @@ "requires": {} }, "sirv": { - "version": "1.0.19", - "resolved": "https://registry.npmjs.org/sirv/-/sirv-1.0.19.tgz", - "integrity": "sha512-JuLThK3TnZG1TAKDwNIqNq6QA2afLOCcm+iE8D1Kj3GA40pSPsxQjjJl0J8X3tsR7T+CP1GavpzLwYkgVLWrZQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.3.tgz", + "integrity": "sha512-O9jm9BsID1P+0HOi81VpXPoDxYP374pkOLzACAoyUQ/3OUVndNpsz6wMnY2z+yOxzbllCKZrM+9QrWsv4THnyA==", "requires": { "@polka/url": "^1.0.0-next.20", "mrmime": "^1.0.0", - "totalist": "^1.0.0" + "totalist": "^3.0.0" } }, "sisteransi": { @@ -80917,7 +80020,7 @@ }, "smart-buffer": { "version": "4.2.0", - "devOptional": true + "dev": true }, "snapdragon": { "version": "0.8.2", @@ -81090,7 +80193,7 @@ }, "socks": { "version": "2.7.1", - "devOptional": true, + "dev": true, "requires": { "ip": "^2.0.0", "smart-buffer": "^4.2.0" @@ -81387,58 +80490,6 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" }, - "stdout-stream": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/stdout-stream/-/stdout-stream-1.4.1.tgz", - "integrity": "sha512-j4emi03KXqJWcIeF8eIXkjMFN1Cmb8gUlDYGeBALLPo5qdyTfA9bOtl8m33lRoC+vFMkP3gl0WsDr6+gzxbbTA==", - "optional": true, - "peer": true, - "requires": { - "readable-stream": "^2.0.1" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "optional": true, - "peer": true - }, - "readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "optional": true, - "peer": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "optional": true, - "peer": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "optional": true, - "peer": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, "stealthy-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", @@ -81607,13 +80658,13 @@ } }, "string.prototype.padstart": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/string.prototype.padstart/-/string.prototype.padstart-3.1.4.tgz", - "integrity": "sha512-XqOHj8horGsF+zwxraBvMTkBFM28sS/jHBJajh17JtJKA92qazidiQbLosV4UA18azvLOVKYo/E3g3T9Y5826w==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/string.prototype.padstart/-/string.prototype.padstart-3.1.5.tgz", + "integrity": "sha512-R57IsE3JIfModQWrVXYZ8ZHWMBNDpIoniDwhYCR1nx+iHwDkjjk26a8xM9BYgf7SAXJO7sdNPng5J+0ccr5LFQ==", "requires": { "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" } }, "string.prototype.trim": { @@ -82712,9 +81763,9 @@ "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==" }, "totalist": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/totalist/-/totalist-1.1.0.tgz", - "integrity": "sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g==" + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==" }, "tough-cookie": { "version": "4.1.2", @@ -82775,7 +81826,7 @@ }, "trim-newlines": { "version": "3.0.1", - "devOptional": true + "dev": true }, "trim-repeated": { "version": "1.0.0", @@ -82798,16 +81849,6 @@ "trough": { "version": "1.0.5" }, - "true-case-path": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-1.0.3.tgz", - "integrity": "sha512-m6s2OdQe5wgpFMC+pAJ+q9djG82O2jcHPOI6RNg1yy9rCYR+WD6Nbpl32fDpfC56nirdRy+opFa/Vk7HYhqaew==", - "optional": true, - "peer": true, - "requires": { - "glob": "^7.1.2" - } - }, "ts-dedent": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz", @@ -82979,6 +82020,39 @@ "mime-types": "~2.1.24" } }, + "typed-array-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz", + "integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==", + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1", + "is-typed-array": "^1.1.10" + } + }, + "typed-array-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz", + "integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==", + "requires": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "has-proto": "^1.0.1", + "is-typed-array": "^1.1.10" + } + }, + "typed-array-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz", + "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==", + "requires": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "has-proto": "^1.0.1", + "is-typed-array": "^1.1.10" + } + }, "typed-array-length": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", @@ -83303,11 +82377,6 @@ "requires-port": "^1.0.0" } }, - "url-resolve-browser": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/url-resolve-browser/-/url-resolve-browser-1.1.0.tgz", - "integrity": "sha512-vxgLZj8ysAZHOiZw6PUG7WGi28OvNqsaQdbZLq54lfZyUEdBGdkfOzc2p3RVhRgdojbsE6lA4Te02fwRyhPXfw==" - }, "url-search-params-polyfill": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/url-search-params-polyfill/-/url-search-params-polyfill-7.0.1.tgz", @@ -83347,6 +82416,12 @@ "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", "requires": {} }, + "usehooks-ts": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/usehooks-ts/-/usehooks-ts-2.9.1.tgz", + "integrity": "sha512-2FAuSIGHlY+apM9FVlj8/oNhd+1y+Uwv5QNkMQz1oSfdHk4PXo1qoCw9I5M7j0vpH8CSWFJwXbVPeYDjLCx9PA==", + "requires": {} + }, "util": { "version": "0.11.1", "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", @@ -83834,14 +82909,13 @@ "integrity": "sha512-wYxSGajtmoP4WxfejAPIr4l0fVh+jeMXZb08wNc0tMg6xsfZXj3cECqIK0G7ZAqUq0PP8WlMDtaOGVBTAWztNw==" }, "web-push-notifications": { - "version": "3.33.0", - "resolved": "https://registry.npmjs.org/web-push-notifications/-/web-push-notifications-3.33.0.tgz", - "integrity": "sha512-9b86KArBi2INVVZQF50ed/ZkW4nUxijJBFmOPy26IEJENXytHUkesvKXGUulGqzbbkLEQRpE8NT0WS5l4RocNQ==", + "version": "3.34.0", + "resolved": "https://registry.npmjs.org/web-push-notifications/-/web-push-notifications-3.34.0.tgz", + "integrity": "sha512-AFwTc19amF57nn3KJ0QDlAVptAP+a1eoc7BwQG5aN//3mvqyexCNKV1Ui3s+e97FdM+qWdNHtoJSdZ1AZyBqSg==", "requires": { "@pushwoosh/logger": "1.0.6", "@pushwoosh/web-push-inbox-widget": "1.0.7", - "@pushwoosh/web-push-subscribe-popup": "1.0.11", - "url-resolve-browser": "1.1.0" + "@pushwoosh/web-push-subscribe-popup": "1.0.11" } }, "webidl-conversions": { @@ -83936,19 +83010,26 @@ } }, "webpack-bundle-analyzer": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.9.0.tgz", - "integrity": "sha512-+bXGmO1LyiNx0i9enBu3H8mv42sj/BJWhZNFwjz92tVnBa9J3JMGo2an2IXlEleoDOPn/Hofl5hr/xCpObUDtw==", + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.9.1.tgz", + "integrity": "sha512-jnd6EoYrf9yMxCyYDPj8eutJvtjQNp8PHmni/e/ulydHBWhT5J3menXt3HEkScsu9YqMAcG4CfFjs3rj5pVU1w==", "requires": { "@discoveryjs/json-ext": "0.5.7", "acorn": "^8.0.4", "acorn-walk": "^8.0.0", - "chalk": "^4.1.0", "commander": "^7.2.0", + "escape-string-regexp": "^4.0.0", "gzip-size": "^6.0.0", - "lodash": "^4.17.20", + "is-plain-object": "^5.0.0", + "lodash.debounce": "^4.0.8", + "lodash.escape": "^4.0.1", + "lodash.flatten": "^4.4.0", + "lodash.invokemap": "^4.6.0", + "lodash.pullall": "^4.2.0", + "lodash.uniqby": "^4.7.0", "opener": "^1.5.2", - "sirv": "^1.0.7", + "picocolors": "^1.0.0", + "sirv": "^2.0.3", "ws": "^7.3.1" }, "dependencies": { @@ -83962,53 +83043,15 @@ "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==" }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, "commander": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==" }, - "has-flag": { + "escape-string-regexp": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" }, "ws": { "version": "7.5.9", @@ -84423,6 +83466,11 @@ "minimist": "^1.2.6" } }, + "node-forge": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", + "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==" + }, "p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -84504,6 +83552,14 @@ "ajv-keywords": "^3.1.0" } }, + "selfsigned": { + "version": "1.10.14", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.14.tgz", + "integrity": "sha512-lkjaiAye+wBZDCBsu5BGi0XiLRxeUlsGod5ZP924CRSEoGuZAw/f7y9RKu28rwTfiHVhdavhB0qH0INV6P1lEA==", + "requires": { + "node-forge": "^0.10.0" + } + }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -84757,9 +83813,9 @@ } }, "whatwg-fetch": { - "version": "3.6.17", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.17.tgz", - "integrity": "sha512-c4ghIvG6th0eudYwKZY5keb81wtFz9/WeAHAoy8+r18kcWlitUIrmGFQ2rWEl4UCKUilD3zCLHOIPheHx5ypRQ==" + "version": "3.6.18", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.18.tgz", + "integrity": "sha512-ltN7j66EneWn5TFDO4L9inYC1D+Czsxlrw2SalgjMmEMkLfA5SIZxEFdE6QtHFiiM6Q7WL32c7AkI3w6yxM84Q==" }, "whatwg-mimetype": { "version": "3.0.0", @@ -84804,14 +83860,15 @@ "version": "2.0.0" }, "which-typed-array": { - "version": "1.1.9", + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.11.tgz", + "integrity": "sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==", "requires": { "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.2", "for-each": "^0.3.3", "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0", - "is-typed-array": "^1.1.10" + "has-tostringtag": "^1.0.0" } }, "wide-align": { @@ -85272,7 +84329,7 @@ }, "yargs": { "version": "17.6.2", - "devOptional": true, + "dev": true, "requires": { "cliui": "^8.0.1", "escalade": "^3.1.1", @@ -85285,7 +84342,7 @@ "dependencies": { "cliui": { "version": "8.0.1", - "devOptional": true, + "dev": true, "requires": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", @@ -85296,7 +84353,7 @@ }, "yargs-parser": { "version": "21.1.1", - "devOptional": true + "dev": true }, "yn": { "version": "3.1.1", diff --git a/package.json b/package.json index ebf619fe94cf..c20126a83750 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "@deriv/eslint-config-deriv": "^1.0.0-beta.3", "@jest/globals": "^26.5.3", "@nrwl/nx-cloud": "latest", + "@playwright/test": "^1.37.1", "@testing-library/jest-dom": "^5.12.0", "@testing-library/react": "^12.0.0", "@testing-library/react-hooks": "^7.0.2", @@ -80,9 +81,9 @@ "test": "f () { bash ./scripts/circleci-config.test.sh && npm run test:stylelint && npm run test:eslint-all && JEST_MAX_WORKERS=6 npm run test:jest ;}; f", "test:stylelint": "stylelint \"./packages/*/src/**/*.s(a|c)ss\"", "test:jest": "jest --all --maxWorkers=${JEST_MAX_WORKERS:-'45%'}", - "test:e2e": "cd end-to-end-test && npx playwright test", - "test:e2e-dev": "cd end-to-end-test && npx playwright test --trace on && npx playwright show-report", - "test:performance": "cd e2e_tests && jest -c ./jest.config.js --maxWorkers=2 --detectOpenHandles performance", + "test:e2e": "npx playwright test", + "test:e2e-dev": "npx playwright test --trace on && npx playwright show-report", + "test:performance": "cd e2e-tests && jest -c ./jest.config.js --maxWorkers=2 --detectOpenHandles performance", "stylelint:fix": "stylelint \"./packages/*/src/**/*.s(a|c)ss\" --fix", "translate": "f () { lerna exec --scope @deriv/translations -- npm run translate ;}; f", "stylelint-check": "stylelint-config-prettier-check", @@ -96,7 +97,9 @@ "dotenv": "^8.2.0", "react": "^17.0.2", "react-dom": "^17.0.2", - "typescript": "^4.6.3" + "selfsigned": "^2.1.1", + "typescript": "^4.6.3", + "ws": "^8.13.0" }, "optionalDependencies": { "fsevents": "^2.3.2" diff --git a/packages/account/build/webpack.config.js b/packages/account/build/webpack.config.js index 4f3fa3a531b5..5b7c955eda22 100644 --- a/packages/account/build/webpack.config.js +++ b/packages/account/build/webpack.config.js @@ -44,7 +44,7 @@ module.exports = function (env) { 'poi-unsupported': 'Components/poi/status/unsupported', 'poi-upload-complete': 'Components/poi/status/upload-complete', 'poi-verified': 'Components/poi/status/verified', - 'proof-of-address-container': 'Sections/Verification/ProofOfAddress/proof-of-address-container.jsx', + 'proof-of-address-container': 'Sections/Verification/ProofOfAddress/proof-of-address-container', 'proof-of-identity': 'Sections/Verification/ProofOfIdentity/proof-of-identity.jsx', 'proof-of-identity-container': 'Sections/Verification/ProofOfIdentity/proof-of-identity-container.jsx', 'proof-of-identity-config': 'Configs/proof-of-identity-config', diff --git a/packages/account/integration-tests/personal-details/personal-details.spec.tsx b/packages/account/integration-tests/personal-details/personal-details.spec.tsx new file mode 100644 index 000000000000..cdcfb63ed6b0 --- /dev/null +++ b/packages/account/integration-tests/personal-details/personal-details.spec.tsx @@ -0,0 +1,21 @@ +import { test, expect } from '@playwright/test'; +import { mock_residents_list, mock_states_list, mock_general, mock_loggedIn, setupMocks } from '@deriv/integration'; + +test.describe('Personal Details', () => { + test('it shows the current name', async ({ page, baseURL }) => { + await setupMocks({ + baseURL, + page, + mocks: [mock_general, mock_loggedIn, mock_residents_list, mock_states_list], + }); + await page.goto(`${baseURL}/account/personal-details`); + const firstName = await page.getByLabel('First name*').first(); + expect(await firstName.inputValue()).toBe('Jane'); + const lastName = await page.getByLabel('Last name*').first(); + expect(await lastName.inputValue()).toBe('Smith'); + const dateOfBirth = await page.getByLabel('Date of birth*').first(); + expect(await dateOfBirth.inputValue()).toBe('01-01-1980'); + const citizenship = await page.getByLabel('Citizenship').first(); + expect(await citizenship.inputValue()).toBe('Thailand'); + }); +}); diff --git a/packages/account/package.json b/packages/account/package.json index 36c035b216e5..c8dc9ccc973e 100644 --- a/packages/account/package.json +++ b/packages/account/package.json @@ -31,6 +31,7 @@ "@deriv/api-types": "^1.0.118", "@deriv/components": "^1.0.0", "@deriv/hooks": "^1.0.0", + "@deriv/integration": "1.0.0", "@deriv/shared": "^1.0.0", "@deriv/stores": "^1.0.0", "@deriv/translations": "^1.0.0", @@ -55,6 +56,7 @@ "@babel/eslint-parser": "^7.17.0", "@babel/preset-react": "^7.16.7", "@jest/globals": "^26.5.3", + "@playwright/test": "^1.37.1", "@testing-library/react": "^12.0.0", "@testing-library/user-event": "^13.5.0", "@types/react": "^18.0.7", diff --git a/packages/account/src/Assets/ic-blurry-document.svg b/packages/account/src/Assets/ic-blurry-document.svg new file mode 100644 index 000000000000..d0cdf47114e8 --- /dev/null +++ b/packages/account/src/Assets/ic-blurry-document.svg @@ -0,0 +1,155 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/account/src/Assets/ic-cropped-document.svg b/packages/account/src/Assets/ic-cropped-document.svg new file mode 100644 index 000000000000..d9914cf734d9 --- /dev/null +++ b/packages/account/src/Assets/ic-cropped-document.svg @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/account/src/Assets/ic-document-address-mismatch.svg b/packages/account/src/Assets/ic-document-address-mismatch.svg new file mode 100644 index 000000000000..d53d8f41fe78 --- /dev/null +++ b/packages/account/src/Assets/ic-document-address-mismatch.svg @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/account/src/Assets/ic-document-name-mismatch.svg b/packages/account/src/Assets/ic-document-name-mismatch.svg new file mode 100644 index 000000000000..427dc50f014d --- /dev/null +++ b/packages/account/src/Assets/ic-document-name-mismatch.svg @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/account/src/Assets/ic-envelop.svg b/packages/account/src/Assets/ic-envelop.svg new file mode 100644 index 000000000000..92d0b680b584 --- /dev/null +++ b/packages/account/src/Assets/ic-envelop.svg @@ -0,0 +1,110 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/account/src/Assets/ic-error-badge.svg b/packages/account/src/Assets/ic-error-badge.svg new file mode 100644 index 000000000000..c06b329f6f62 --- /dev/null +++ b/packages/account/src/Assets/ic-error-badge.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/account/src/Assets/ic-old-issued-document.svg b/packages/account/src/Assets/ic-old-issued-document.svg new file mode 100644 index 000000000000..7bd48aaf3630 --- /dev/null +++ b/packages/account/src/Assets/ic-old-issued-document.svg @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/account/src/Components/address-details/__tests__/address-details.spec.tsx b/packages/account/src/Components/address-details/__tests__/address-details.spec.tsx index 1deb87a55a92..f460ad5fab61 100644 --- a/packages/account/src/Components/address-details/__tests__/address-details.spec.tsx +++ b/packages/account/src/Components/address-details/__tests__/address-details.spec.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import AddressDetails from '../address-details'; -import { isDesktop, isMobile, PlatformContext, TLocationList } from '@deriv/shared'; +import { isDesktop, isMobile, PlatformContext } from '@deriv/shared'; import { FormikProps, FormikValues } from 'formik'; jest.mock('@deriv/shared', () => ({ @@ -40,6 +40,7 @@ describe('', () => { getCurrentStep: jest.fn(), goToNextStep: jest.fn(), goToPreviousStep: jest.fn(), + has_real_account: false, is_gb_residence: '', is_svg: true, onCancel: jest.fn(), @@ -102,9 +103,9 @@ describe('', () => { expect(screen.queryByText(verification_info)).not.toBeInTheDocument(); const inputs: HTMLTextAreaElement[] = screen.getAllByRole('textbox'); - expect(inputs.length).toBe(5); + expect(inputs).toHaveLength(5); const required_fields = inputs.filter(input => input.required === true); - expect(required_fields.length).toBe(2); + expect(required_fields).toHaveLength(2); }); it('should render AddressDetails component and trigger buttons', async () => { @@ -116,10 +117,10 @@ describe('', () => { expect(screen.queryByText(verification_info)).not.toBeInTheDocument(); const inputs: HTMLTextAreaElement[] = screen.getAllByRole('textbox'); - expect(inputs.length).toBe(5); + expect(inputs).toHaveLength(5); const required_fields = inputs.filter(input => input.required === true); - expect(required_fields.length).toBe(2); + expect(required_fields).toHaveLength(2); const previous_btn = screen.getByRole('button', { name: /previous/i }); fireEvent.click(previous_btn); @@ -175,9 +176,9 @@ describe('', () => { expect(mock_props.onSubmitEnabledChange).toHaveBeenCalledTimes(1); const inputs: HTMLTextAreaElement[] = screen.getAllByRole('textbox'); - expect(inputs.length).toBe(5); + expect(inputs).toHaveLength(5); const required_fields = inputs.filter(input => input.required === true); - expect(required_fields.length).toBe(0); + expect(required_fields).toHaveLength(0); await waitFor(() => { expect(screen.getByLabelText(address_line_1)).toBeInTheDocument(); @@ -209,10 +210,10 @@ describe('', () => { expect(screen.queryByText(use_address_info)).not.toBeInTheDocument(); const inputs: HTMLTextAreaElement[] = screen.getAllByRole('textbox'); - expect(inputs.length).toBe(5); + expect(inputs).toHaveLength(5); const required_fields = inputs.filter(input => input.required === true); - expect(required_fields.length).toBe(4); + expect(required_fields).toHaveLength(4); expect(screen.getByLabelText(address_line_1_marked)).toBeInTheDocument(); expect(screen.getByLabelText(address_line_2_marked)).toBeInTheDocument(); @@ -235,7 +236,7 @@ describe('', () => { mock_props.states_list = [ { text: 'State 1', value: 'State 1' }, { text: 'State 2', value: 'State 2' }, - ] as TLocationList[]; + ]; render(); @@ -253,7 +254,7 @@ describe('', () => { mock_props.states_list = [ { text: 'State 1', value: 'State 1' }, { text: 'State 2', value: 'State 2' }, - ] as TLocationList[]; + ]; render(); diff --git a/packages/account/src/Components/address-details/address-details.tsx b/packages/account/src/Components/address-details/address-details.tsx index 708781a99b07..8b7320bb3a73 100644 --- a/packages/account/src/Components/address-details/address-details.tsx +++ b/packages/account/src/Components/address-details/address-details.tsx @@ -1,5 +1,6 @@ import { Formik, Field, FormikProps, FormikValues } from 'formik'; import React from 'react'; +import { StatesList } from '@deriv/api-types'; import { Modal, Autocomplete, @@ -15,19 +16,13 @@ import { Text, } from '@deriv/components'; import { localize, Localize } from '@deriv/translations'; -import { - isDesktop, - isMobile, - getLocation, - makeCancellablePromise, - PlatformContext, - TLocationList, -} from '@deriv/shared'; +import { isDesktop, isMobile, getLocation, makeCancellablePromise, PlatformContext } from '@deriv/shared'; import { splitValidationResultTypes } from '../real-account-signup/helpers/utils'; import classNames from 'classnames'; type TAddressDetails = { - states_list: TLocationList[]; + disabled_items: string[]; + states_list: StatesList; getCurrentStep?: () => number; onSave: (current_step: number, values: FormikValues) => void; onCancel: (current_step: number, goToPreviousStep: () => void) => void; @@ -43,17 +38,13 @@ type TAddressDetails = { is_svg: boolean; is_mf?: boolean; is_gb_residence: boolean | string; + has_real_account: boolean; onSubmitEnabledChange: (is_submit_disabled: boolean) => void; selected_step_ref?: React.RefObject>; fetchStatesList: () => Promise; value: FormikValues; }; -type TFormValidation = { - warnings: { [key: string]: string }; - errors: { [key: string]: string }; -}; - type TInputField = { name: string; required?: boolean | string; @@ -131,7 +122,7 @@ const AddressDetails = ({ return selected_step_ref?.current?.isSubmitting || (errors && Object.keys(errors).length > 0); }; - const checkSubmitStatus = (errors?: { [key: string]: string }) => { + const checkSubmitStatus = (errors?: { [key: string]: string } | FormikValues) => { const is_submit_disabled = isSubmitDisabled(errors); if (is_submit_disabled_ref.current !== is_submit_disabled) { @@ -147,7 +138,7 @@ const AddressDetails = ({ }; const handleValidate = (values: FormikValues) => { - const { errors }: Partial = splitValidationResultTypes(validate(values)); + const { errors } = splitValidationResultTypes(validate(values)); checkSubmitStatus(errors); return errors; }; diff --git a/packages/account/src/Components/demo-message/demo-message.tsx b/packages/account/src/Components/demo-message/demo-message.tsx index f2f379ebc433..796e2d757bb8 100644 --- a/packages/account/src/Components/demo-message/demo-message.tsx +++ b/packages/account/src/Components/demo-message/demo-message.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { localize } from '@deriv/translations'; -import IconWithMessage from 'Components/icon-with-message'; +import IconWithMessage from '../icon-with-message'; type TDemoMessage = { has_demo_icon?: boolean; diff --git a/packages/account/src/Components/file-uploader-container/__tests__/file-uploader-container.spec.tsx b/packages/account/src/Components/file-uploader-container/__tests__/file-uploader-container.spec.tsx index f130d2324d37..36e69d3709a1 100644 --- a/packages/account/src/Components/file-uploader-container/__tests__/file-uploader-container.spec.tsx +++ b/packages/account/src/Components/file-uploader-container/__tests__/file-uploader-container.spec.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; -import { isDesktop, isMobile, PlatformContext } from '@deriv/shared'; -import FileUploaderContainer, { TFileUploaderContainer } from '../file-uploader-container'; +import { isDesktop, isMobile } from '@deriv/shared'; +import FileUploaderContainer from '../file-uploader-container'; jest.mock('@deriv/components', () => { const original_module = jest.requireActual('@deriv/components'); @@ -20,107 +20,75 @@ jest.mock('@deriv/shared', () => ({ })); describe('', () => { + let mock_props: React.ComponentProps; + beforeEach(() => { - isDesktop.mockReturnValue(true); - isMobile.mockReturnValue(false); + mock_props = { + examples: '', + files_description: '', + getSocket: jest.fn(), + onFileDrop: jest.fn(), + onRef: jest.fn(), + settings: {}, + }; + + (isDesktop as jest.Mock).mockReturnValue(true); + (isMobile as jest.Mock).mockReturnValue(false); jest.clearAllMocks(); }); - const props: TFileUploaderContainer = { - getSocket: jest.fn(), - onFileDrop: jest.fn(), - onRef: jest.fn(), - settings: {}, - }; - - const file_size_msg = /less than 8mb/i; - const file_type_msg = /jpeg jpg png pdf gif/i; - const file_time_msg = /1 \- 6 months old/i; - const file_clear_msg = /a clear colour photo or scanned image/i; - const file_address = /issued under your name with your current address/i; + const file_size_msg = /maximum size: 8MB/i; + const file_type_msg = /supported formats: JPEG, JPG, PNG, PDF and GIF only/i; + const file_warning_msg = /remember, selfies, pictures of houses, or non-related images will be rejected./i; + const hint_msg_desktop = /drag and drop a file or click to browse your files/i; + const hint_msg_mobile = /click here to upload/i; const runCommonTests = () => { - expect(screen.getAllByText('mockedIcon')).toHaveLength(6); + expect(screen.getByTestId('dt_file_uploader_container')).toBeInTheDocument(); + expect(screen.getByText('mockedIcon')).toBeInTheDocument(); expect(screen.getByText(file_size_msg)).toBeInTheDocument(); expect(screen.getByText(file_type_msg)).toBeInTheDocument(); - expect(screen.getByText(file_time_msg)).toBeInTheDocument(); - expect(screen.getByText(file_clear_msg)).toBeInTheDocument(); - expect(screen.getByText(file_address)).toBeInTheDocument(); + expect(screen.getByText(file_warning_msg)).toBeInTheDocument(); }; - it('should render FileUploaderContainer component', () => { - render(); - expect(screen.getByTestId('dt_file_uploader_container')).toBeInTheDocument(); - }); - - it('should render FileUploaderContainer component if getSocket is not passed as prop', () => { - render(); - expect(screen.getByTestId('dt_file_uploader_container')).toBeInTheDocument(); - }); - - it('should not render FileUploaderContainer when is_appstore is true in desktop', () => { - render( - - - - ); - expect(screen.queryByTestId('dt_file_uploader_container')).not.toBeInTheDocument(); - }); - it('should show icons and description when is_appstore is true in desktop', () => { - render( - - - - ); + it('should render FileUploaderContainer component and show descriptions', () => { + render(); runCommonTests(); }); - it('should show description when is_appstore false in desktop', () => { - render( - - - - ); + it('should render FileUploaderContainer component if getSocket is not passed as prop', () => { + delete mock_props.getSocket; + render(); runCommonTests(); }); - it('should show description when is_appstore true in mobile', () => { - isMobile.mockReturnValue(true); - isDesktop.mockReturnValue(false); + it('files description and examples should be shown when passed', () => { + mock_props.files_description =
Files description
; + mock_props.examples =
Files failure examples
; - render( - - - - ); - runCommonTests(); + render(); + expect(screen.getByText('Files description')).toBeInTheDocument(); + expect(screen.getByText('Files failure examples')).toBeInTheDocument(); }); - it('should not show description if is_description_enabled is false)', () => { - isMobile.mockReturnValue(true); - isDesktop.mockReturnValue(false); + it('should show hint message for desktop', () => { + render(); + expect(screen.getByText(hint_msg_desktop)).toBeInTheDocument(); + expect(screen.queryByText(hint_msg_mobile)).not.toBeInTheDocument(); + }); - render( - - - - ); + it('should show hint message for mobile', () => { + (isMobile as jest.Mock).mockReturnValue(true); + (isDesktop as jest.Mock).mockReturnValue(false); - expect(screen.getByText('mockedIcon')).toBeInTheDocument(); - expect(screen.queryByText(file_size_msg)).not.toBeInTheDocument(); - expect(screen.queryByText(file_type_msg)).not.toBeInTheDocument(); - expect(screen.queryByText(file_time_msg)).not.toBeInTheDocument(); - expect(screen.queryByText(file_clear_msg)).not.toBeInTheDocument(); - expect(screen.queryByText(file_address)).not.toBeInTheDocument(); + render(); + expect(screen.getByText(hint_msg_mobile)).toBeInTheDocument(); + expect(screen.queryByText(hint_msg_desktop)).not.toBeInTheDocument(); }); it('should call ref function on rendering the component', () => { - render( - - - - ); + render(); - expect(props.onRef).toHaveBeenCalled(); + expect(mock_props.onRef).toHaveBeenCalled(); }); }); diff --git a/packages/account/src/Components/file-uploader-container/__tests__/file-uploader.spec.tsx b/packages/account/src/Components/file-uploader-container/__tests__/file-uploader.spec.tsx index ea1245d225b4..67fd1a236600 100644 --- a/packages/account/src/Components/file-uploader-container/__tests__/file-uploader.spec.tsx +++ b/packages/account/src/Components/file-uploader-container/__tests__/file-uploader.spec.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { render, screen, fireEvent, waitFor } from '@testing-library/react'; -import { compressImageFiles, isMobile, isDesktop, readFiles, TSettings } from '@deriv/shared'; +import { DocumentUploadResponse } from '@deriv/api-types'; +import { compressImageFiles, isMobile, isDesktop, readFiles } from '@deriv/shared'; import FileUploader from '../file-uploader'; jest.mock('@deriv/shared', () => ({ @@ -15,17 +16,12 @@ jest.mock('@binary-com/binary-document-uploader'); describe('', () => { beforeEach(() => { - isDesktop.mockReturnValue(true); - isMobile.mockReturnValue(false); + (isDesktop as jest.Mock).mockReturnValue(true); + (isMobile as jest.Mock).mockReturnValue(false); jest.clearAllMocks(); }); - const props: { - onFileDrop: (file: File | undefined) => void; - getSocket: () => WebSocket; - ref: React.RefObject; - settings: TSettings; - } = { + const props: React.ComponentProps = { onFileDrop: jest.fn(), getSocket: jest.fn(), ref: React.createRef(), @@ -34,7 +30,7 @@ describe('', () => { const large_file_error_msg = /file size should be 8mb or less/i; const file_not_supported_msg = /file uploaded is not supported/i; - const drop_click_msg = /drop file or click here to upload/i; + const drop_click_msg = /drag and drop a file or click to browse your files/i; const click_msg = /click here to upload/i; it('should render FileUploader component in desktop mode', () => { @@ -43,8 +39,8 @@ describe('', () => { }); it('should render FileUploader component in mobile mode', () => { - isMobile.mockReturnValue(true); - isDesktop.mockReturnValue(false); + (isMobile as jest.Mock).mockReturnValue(true); + (isDesktop as jest.Mock).mockReturnValue(false); render(); expect(screen.getByText(click_msg)).toBeInTheDocument(); }); @@ -128,12 +124,14 @@ describe('', () => { it('upload function should return 0 if document is not selected', () => { render(); - const uploadFn = props.ref.current.upload(); + const uploadFn = ( + props?.ref as React.RefObject Promise }> + ).current?.upload(); expect(uploadFn).toBe(0); }); it('upload methods should reject if readFile returns empty array ', async () => { - readFiles.mockResolvedValue([]); + (readFiles as jest.Mock).mockResolvedValue([]); render(); const blob = new Blob(['sample_data']); @@ -145,7 +143,9 @@ describe('', () => { expect(screen.getByText(/hello\.pdf/i)).toBeInTheDocument(); expect(input?.files?.[0]).toBe(file); }); - props.ref.current.upload(); + ( + props?.ref as React.RefObject Promise }> + ).current?.upload(); expect(compressImageFiles).toBeCalled(); expect(props.onFileDrop).toBeCalled(); }); diff --git a/packages/account/src/Components/file-uploader-container/file-uploader-container.tsx b/packages/account/src/Components/file-uploader-container/file-uploader-container.tsx index de6375f07109..ddc023442493 100644 --- a/packages/account/src/Components/file-uploader-container/file-uploader-container.tsx +++ b/packages/account/src/Components/file-uploader-container/file-uploader-container.tsx @@ -1,59 +1,29 @@ import React from 'react'; -import classNames from 'classnames'; -import { Icon, Text } from '@deriv/components'; -import { PlatformContext, isDesktop, WS, TSettings } from '@deriv/shared'; -import { Localize, localize } from '@deriv/translations'; +import { Text } from '@deriv/components'; +import { isMobile, WS } from '@deriv/shared'; +import type { TSettings } from '@deriv/shared/src/utils/files/file-uploader-utils'; +import { Localize } from '@deriv/translations'; import FileUploader from './file-uploader'; -import { TFile, TPlatformContext } from 'Types'; +import { TFile } from '../../Types'; -export type TFileUploaderContainer = { - is_description_enabled?: boolean; - getSocket: () => WebSocket; +type TFileUploaderContainer = { + getSocket?: () => WebSocket; onFileDrop: (file: TFile | undefined) => void; - onRef: (ref: React.RefObject | undefined) => void; - settings: TSettings; -}; - -const FileProperties = () => { - const properties = [ - { name: 'size', icon: 'IcPoaFileEightMb', text: localize('Less than 8MB') }, - { name: 'format', icon: 'IcPoaFileFormat', text: localize('JPEG JPG PNG PDF GIF') }, - { name: 'time', icon: 'IcPoaFileTime', text: localize('1 - 6 months old') }, - { name: 'clear', icon: 'IcPoaFileClear', text: localize('A clear colour photo or scanned image') }, - { - name: 'with-address', - icon: 'IcPoaFileWithAddress', - text: localize('Issued under your name with your current address'), - }, - ]; - return ( -
- {properties.map(item => ( -
-
- - - {item.text} - -
-
- ))} -
- ); + onRef: (ref: React.RefObject void }> | undefined) => void; + settings?: Partial; + files_description: React.ReactNode; + examples: React.ReactNode; }; const FileUploaderContainer = ({ - is_description_enabled = true, + examples, + files_description, getSocket, onFileDrop, onRef, settings, }: TFileUploaderContainer) => { - const { is_appstore } = React.useContext>(PlatformContext); - const ref = React.useRef(); + const ref = React.useRef(null); const getSocketFunc = getSocket ?? WS.getSocket; @@ -63,77 +33,25 @@ const FileUploaderContainer = ({ } return () => onRef(undefined); }, [onRef, ref]); - if (is_appstore && isDesktop()) { - return ( -
-
- -
- -
-
-
- ); - } + return ( -
- {is_description_enabled && ( -
    -
  • - {is_appstore ? ( - - ) : ( - - )} -
    - -
    -
  • -
  • - -
    - -
    -
  • -
  • - {is_appstore ? ( - - ) : ( - - )} -
    - -
    -
  • -
  • - {is_appstore ? ( - - ) : ( - - )} -
    - -
    -
  • -
  • - {is_appstore ? ( - - ) : ( - - )} -
    - -
    -
  • -
- )} -
+
+ {files_description} + + + +
+
+ + + + + + +
+ {examples}
); }; diff --git a/packages/account/src/Components/file-uploader-container/file-uploader.tsx b/packages/account/src/Components/file-uploader-container/file-uploader.tsx index 4efefe6d25b1..db425b312c67 100644 --- a/packages/account/src/Components/file-uploader-container/file-uploader.tsx +++ b/packages/account/src/Components/file-uploader-container/file-uploader.tsx @@ -1,8 +1,11 @@ +//TODO all file upload process has to be checked and refactored with TS. skipping checks for passing CFD build +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-nocheck import React from 'react'; import classNames from 'classnames'; import DocumentUploader from '@binary-com/binary-document-uploader'; -import { FileDropzone, Icon, useStateCallback } from '@deriv/components'; -import { localize } from '@deriv/translations'; +import { FileDropzone, Icon, Text, useStateCallback } from '@deriv/components'; +import { Localize, localize } from '@deriv/translations'; import { isMobile, compressImageFiles, @@ -21,9 +24,18 @@ type TFileObject = { const UploadMessage = () => { return ( - +
- {isMobile() ? localize('Click here to upload') : localize('Drop file or click here to upload')} + + {isMobile() ? ( + + ) : ( + + )} + + + +
); @@ -35,7 +47,7 @@ const fileReadErrorMessage = (filename: string) => { const FileUploader = React.forwardRef< HTMLElement, - { onFileDrop: (file: TFile | undefined) => void; getSocket: () => WebSocket; settings: TSettings } + { onFileDrop: (file: TFile | undefined) => void; getSocket: () => WebSocket; settings?: Partial } >(({ onFileDrop, getSocket, settings = {} }, ref) => { const [document_file, setDocumentFile] = useStateCallback({ files: [], error_message: null }); @@ -121,11 +133,11 @@ const FileUploader = React.forwardRef< value={document_file.files} /> {(document_file.files.length > 0 || !!document_file.error_message) && ( -
+
) => { - const { is_appstore } = React.useContext(PlatformContext); - if (has_side_note) { return ( -
+
{typeof side_note === 'string' ? ( diff --git a/packages/account/src/Components/form-body/form-body.tsx b/packages/account/src/Components/form-body/form-body.tsx index a216f6f7ef3a..6806f15b33d8 100644 --- a/packages/account/src/Components/form-body/form-body.tsx +++ b/packages/account/src/Components/form-body/form-body.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { ScrollbarsContainer } from 'Components/scrollbars-container/scrollbars-container'; +import { ScrollbarsContainer } from '../scrollbars-container/scrollbars-container'; import { Div100vhContainer, DesktopWrapper, MobileWrapper } from '@deriv/components'; type TFormBody = { diff --git a/packages/account/src/Components/form-footer/form-footer.tsx b/packages/account/src/Components/form-footer/form-footer.tsx index cd26de539eea..1e30d8fc801e 100644 --- a/packages/account/src/Components/form-footer/form-footer.tsx +++ b/packages/account/src/Components/form-footer/form-footer.tsx @@ -1,24 +1,14 @@ import React from 'react'; -import { PlatformContext } from '@deriv/shared'; import classNames from 'classnames'; export type TFormFooter = { className?: string; }; -const FormFooter = ({ children, className }: React.PropsWithChildren) => { - const { is_appstore } = React.useContext(PlatformContext); - - return ( -
- {children} -
- ); -}; +const FormFooter = ({ children, className }: React.PropsWithChildren) => ( +
+ {children} +
+); export default FormFooter; diff --git a/packages/account/src/Components/form-sub-header/form-sub-header.tsx b/packages/account/src/Components/form-sub-header/form-sub-header.tsx index 747613640a3a..935e799b1746 100644 --- a/packages/account/src/Components/form-sub-header/form-sub-header.tsx +++ b/packages/account/src/Components/form-sub-header/form-sub-header.tsx @@ -6,10 +6,12 @@ export type TFormSubHeader = { description?: string; subtitle?: string; title: string; + title_text_size?: string; }; -export const FormSubHeader = ({ description, subtitle, title }: TFormSubHeader) => { +export const FormSubHeader = ({ description, subtitle, title, title_text_size = 'xs' }: TFormSubHeader) => { const title_as_class = title.replace(/\s+/g, '-').toLowerCase(); + return (
- + {title} {subtitle && ( diff --git a/packages/account/src/Components/forms/form-fields.jsx b/packages/account/src/Components/forms/form-fields.jsx index 00f0f728df19..d7b7fe337747 100644 --- a/packages/account/src/Components/forms/form-fields.jsx +++ b/packages/account/src/Components/forms/form-fields.jsx @@ -21,12 +21,11 @@ export const DateOfBirthField = ({ name, portal_id, ...rest }) => ( ); -export const FormInputField = ({ name, optional = false, warn, ...rest }) => ( +export const FormInputField = ({ name, warn, ...rest }) => ( {({ field, form: { errors, touched } }) => ( { - const [document_list, setDocumentList] = React.useState([]); + const [document_list, setDocumentList] = React.useState([]); const [document_image, setDocumentImage] = React.useState(null); const [selected_doc, setSelectedDoc] = React.useState(''); @@ -45,10 +50,8 @@ const IDVForm = ({ const new_document_list = filtered_documents.map(key => { const { display_name, format } = document_data[key]; - const { new_display_name, example_format, sample_image } = getDocumentData( - selected_country.value ?? '', - key - ); + const { new_display_name, example_format, sample_image, additional_document_example_format } = + getDocumentData(selected_country.value ?? '', key); const needs_additional_document = !!document_data[key].additional; if (needs_additional_document) { @@ -58,6 +61,7 @@ const IDVForm = ({ additional: { display_name: document_data[key].additional?.display_name, format: document_data[key].additional?.format, + example_format: additional_document_example_format, }, value: format, sample_image, @@ -98,7 +102,7 @@ const IDVForm = ({ setFieldValue(document_name, current_input, true); }; - const bindDocumentData = (item: TDocumentList) => { + const bindDocumentData = (item: TDocument) => { setFieldValue('document_type', item, true); setSelectedDoc(item?.id); if (item?.id === IDV_NOT_APPLICABLE_OPTION.id) { @@ -189,76 +193,75 @@ const IDVForm = ({ )} -
- - {({ field }: FieldProps) => ( - - - onKeyUp(e, 'document_number') - } - required - label={generatePlaceholderText(selected_doc)} - /> - {values.document_type.additional?.display_name && ( + {values.document_type.id !== IDV_NOT_APPLICABLE_OPTION.id && ( +
+ + {({ field }: FieldProps) => ( + - onKeyUp(e, 'document_additional') + onKeyUp(e, 'document_number') } + className='additional-field' required + label={generatePlaceholderText(selected_doc)} /> - )} - - )} - -
+ {values.document_type.additional?.display_name && ( + + onKeyUp(e, 'document_additional') + } + required + /> + )} +
+ )} +
+
+ )}
{document_image && (
diff --git a/packages/account/src/Components/forms/personal-details-form.jsx b/packages/account/src/Components/forms/personal-details-form.jsx index 00efe40bcfa6..4eafaca31919 100644 --- a/packages/account/src/Components/forms/personal-details-form.jsx +++ b/packages/account/src/Components/forms/personal-details-form.jsx @@ -1,6 +1,7 @@ import React from 'react'; -import { Field, useFormikContext } from 'formik'; import classNames from 'classnames'; +import { Field, useFormikContext } from 'formik'; +import { Link } from 'react-router-dom'; import { Autocomplete, Checkbox, @@ -14,14 +15,13 @@ import { } from '@deriv/components'; import { getLegalEntityName, isDesktop, isMobile, routes, validPhone } from '@deriv/shared'; import { Localize, localize } from '@deriv/translations'; -import FormSubHeader from 'Components/form-sub-header'; -import PoiNameDobExample from 'Assets/ic-poi-name-dob-example.svg'; -import InlineNoteWithIcon from 'Components/inline-note-with-icon'; -import FormBodySection from 'Components/form-body-section'; -import { DateOfBirthField, FormInputField } from 'Components/forms/form-fields'; -import { Link } from 'react-router-dom'; -import { getEmploymentStatusList } from 'Sections/Assessment/FinancialAssessment/financial-information-list'; -import { isFieldImmutable } from 'Helpers/utils'; +import FormSubHeader from '../form-sub-header'; +import PoiNameDobExample from '../../Assets/ic-poi-name-dob-example.svg'; +import InlineNoteWithIcon from '../inline-note-with-icon'; +import FormBodySection from '../form-body-section'; +import { DateOfBirthField, FormInputField } from './form-fields.jsx'; +import { getEmploymentStatusList } from '../../Sections/Assessment/FinancialAssessment/financial-information-list'; +import { isFieldImmutable } from '../../Helpers/utils'; const PersonalDetailsForm = props => { const { @@ -30,7 +30,6 @@ const PersonalDetailsForm = props => { is_svg, is_qualified_for_idv, should_hide_helper_image, - is_appstore, editable_fields = [], has_real_account, residence_list, @@ -39,9 +38,11 @@ const PersonalDetailsForm = props => { closeRealAccountSignup, salutation_list, is_rendered_for_onfido, + is_qualified_for_poa, should_close_tooltip, setShouldCloseTooltip, class_name, + states_list, } = props; const autocomplete_value = 'none'; const PoiNameDobExampleIcon = PoiNameDobExample; @@ -60,10 +61,9 @@ const PersonalDetailsForm = props => { const getNameAndDobLabels = () => { const is_asterisk_needed = is_svg || is_mf || is_rendered_for_onfido || is_qualified_for_idv; - const first_name_label = is_appstore || is_asterisk_needed ? localize('First name*') : localize('First name'); - const last_name_text = is_asterisk_needed ? localize('Last name*') : localize('Last name'); - const last_name_label = is_appstore ? localize('Family name*') : last_name_text; - const dob_label = is_appstore || is_asterisk_needed ? localize('Date of birth*') : localize('Date of birth'); + const first_name_label = is_asterisk_needed ? localize('First name*') : localize('First name'); + const last_name_label = is_asterisk_needed ? localize('Last name*') : localize('Last name'); + const dob_label = is_asterisk_needed ? localize('Date of birth*') : localize('Date of birth'); return { first_name_label, @@ -101,6 +101,10 @@ const PersonalDetailsForm = props => { /> ); + const poa_clarification_message = ( + + ); + // need to put this check related to DIEL clients const is_svg_only = is_svg && !is_mf; @@ -117,6 +121,13 @@ const PersonalDetailsForm = props => { font_size={isMobile() ? 'xxxs' : 'xs'} /> )} + {is_qualified_for_poa && ( + + )} } @@ -145,7 +156,7 @@ const PersonalDetailsForm = props => {
)} - {!is_qualified_for_idv && !is_appstore && !is_rendered_for_onfido && ( + {!is_qualified_for_idv && !is_rendered_for_onfido && !is_qualified_for_poa && ( @@ -176,7 +187,7 @@ const PersonalDetailsForm = props => { {'first_name' in values && ( { {'last_name' in values && ( { data-testid='last_name' /> )} - {!is_appstore && !is_qualified_for_idv && !is_rendered_for_onfido && ( + {!is_qualified_for_idv && !is_rendered_for_onfido && !is_qualified_for_poa && ( )} {'date_of_birth' in values && ( { (values?.date_of_birth && has_real_account) } placeholder={localize('01-07-1999')} - portal_id={is_appstore ? '' : 'modal_root'} + portal_id='modal_root' data_testid='date_of_birth' /> )} + {'address_line_1' in values && ( + + )} + {'address_line_2' in values && ( + + )} + {'address_city' in values && ( + + )} + {'address_state' in values && + (states_list?.length ? ( + + + + {({ field }) => ( + + setFieldValue('address_state', value ? text : '', true) + } + /> + )} + + + + setFieldValue('address_state', e.target.value, true)} + /> + + + ) : ( + + ))} + {'address_postcode' in values && ( + + )} {!is_svg_only && 'place_of_birth' in values && ( { label={is_mf ? localize('Citizenship*') : localize('Citizenship')} list_items={residence_list} value={values.citizen} - use_text={true} + use_text error={touched.citizen && errors.citizen} onChange={e => { handleChange(e); @@ -296,10 +419,7 @@ const PersonalDetailsForm = props => { {'tax_residence' in values && ( { is_tin_popover_open={is_tin_popover_open} setIsTinPopoverOpen={setIsTinPopoverOpen} setIsTaxResidencePopoverOpen={setIsTaxResidencePopoverOpen} - disabled={ - isFieldImmutable('tax_identification_number', editable_fields) || - (values?.tax_identification_number && has_real_account) - } + disabled={isFieldImmutable('tax_identification_number', editable_fields)} required /> )} @@ -428,12 +545,8 @@ const PersonalDetailsForm = props => { {'tax_residence' in values && ( { is_tin_popover_open={is_tin_popover_open} setIsTinPopoverOpen={setIsTinPopoverOpen} setIsTaxResidencePopoverOpen={setIsTaxResidencePopoverOpen} - disabled={ - isFieldImmutable('tax_identification_number', editable_fields) || - (values?.tax_identification_number && has_real_account) - } - required + disabled={isFieldImmutable('tax_identification_number', editable_fields)} /> )} {'account_opening_reason' in values && ( @@ -513,7 +622,7 @@ const PlaceOfBirthField = ({ handleChange, setFieldValue, disabled, residence_li label={required ? localize('Place of birth*') : localize('Place of birth')} list_items={residence_list} value={field.value} - use_text={true} + use_text error={meta.touched && meta.error} onChange={e => { handleChange(e); @@ -534,7 +643,7 @@ const PlaceOfBirthField = ({ handleChange, setFieldValue, disabled, residence_li const TaxResidenceField = ({ setFieldValue, residence_list, - required, + required = false, setIsTaxResidencePopoverOpen, setIsTinPopoverOpen, is_tax_residence_popover_open, @@ -556,6 +665,7 @@ const TaxResidenceField = ({ list_portal_id='modal_root' data-testid='tax_residence' disabled={disabled} + required={required} /> @@ -565,14 +675,14 @@ const TaxResidenceField = ({ label={required ? localize('Tax residence*') : localize('Tax residence')} list_items={residence_list} value={field.value} - use_text={true} + use_text error={meta.touched && meta.error} onChange={e => { field.onChange(e); setFieldValue('tax_residence', e.target.value, true); }} {...field} - required + required={required} data_testid='tax_residence_mobile' disabled={disabled} /> @@ -606,7 +716,7 @@ const TaxIdentificationNumberField = ({ setIsTinPopoverOpen, setIsTaxResidencePopoverOpen, disabled, - required, + required = false, }) => (
+
{ it('should show proper icon', () => { get_leave_confirm_states(); render(); - expect(screen.getByTestId('unsaved_changes_icon')).toBeInTheDocument(); + expect(screen.getByTestId('dt_unsaved_changes_icon')).toBeInTheDocument(); expect(screen.getByText('Unsaved changes')).toBeInTheDocument(); expect( screen.getByText('You have unsaved changes. Are you sure you want to discard changes and leave this page?') diff --git a/packages/account/src/Components/leave-confirm/leave-confirm.tsx b/packages/account/src/Components/leave-confirm/leave-confirm.tsx index fb60e32afad5..03ef8b01da24 100644 --- a/packages/account/src/Components/leave-confirm/leave-confirm.tsx +++ b/packages/account/src/Components/leave-confirm/leave-confirm.tsx @@ -1,36 +1,28 @@ import React from 'react'; -import { useHistory, withRouter } from 'react-router-dom'; +import { RouteComponentProps, useHistory, withRouter } from 'react-router-dom'; import { FormikConsumer } from 'formik'; import { Button, Icon, Modal } from '@deriv/components'; -import { isMobile, PlatformContext } from '@deriv/shared'; +import { isMobile } from '@deriv/shared'; import { localize } from '@deriv/translations'; -import IconMessageContent from 'Components/icon-message-content'; +import IconMessageContent from '../icon-message-content'; type TLeaveConfirmMessage = { back: () => void; leave: () => void; }; -type TTransitionBlocker = { +type TTransitionBlocker = RouteComponentProps & { dirty: boolean; - onDirty: (prop: boolean) => void; + onDirty?: (prop: boolean) => void; }; const LeaveConfirmMessage = ({ back, leave }: TLeaveConfirmMessage) => { - const { is_appstore } = React.useContext(PlatformContext); - return ( - } + icon={} >
- ) : null; - - if (is_loading) return ; - if ( - !allow_document_upload || - (!is_age_verified && !allow_poa_resubmission && document_status === 'none' && is_mx_mlt) - ) - return ; - if (has_submitted_poa && !poa_address_mismatch) - return ; - if ( - resubmit_poa || - allow_poa_resubmission || - (has_restricted_mt5_account && ['expired', 'rejected', 'suspected'].includes(document_status)) || - poa_address_mismatch - ) { - return onSubmit({ needs_poi })} />; - } - - switch (document_status) { - case PoaStatusCodes.none: - return onSubmit({ needs_poi })} />; - case PoaStatusCodes.pending: - return ; - case PoaStatusCodes.verified: - return ; - case PoaStatusCodes.expired: - return ; - case PoaStatusCodes.rejected: - case PoaStatusCodes.suspected: - return ; - default: - return null; - } -}; - -ProofOfAddressContainer.propTypes = { - is_mx_mlt: PropTypes.bool, - has_restricted_mt5_account: PropTypes.bool, - is_switching: PropTypes.bool, - refreshNotifications: PropTypes.func, -}; - -export default ProofOfAddressContainer; diff --git a/packages/account/src/Sections/Verification/ProofOfAddress/proof-of-address-container.tsx b/packages/account/src/Sections/Verification/ProofOfAddress/proof-of-address-container.tsx new file mode 100644 index 000000000000..be2b2ed2f113 --- /dev/null +++ b/packages/account/src/Sections/Verification/ProofOfAddress/proof-of-address-container.tsx @@ -0,0 +1,166 @@ +import React from 'react'; +import { AccountStatusResponse, GetAccountStatus } from '@deriv/api-types'; +import { Button, Loading } from '@deriv/components'; +import { WS, getPlatformRedirect, platforms } from '@deriv/shared'; +import { observer, useStore } from '@deriv/stores'; +import { Localize } from '@deriv/translations'; +import Expired from '../../../Components/poa/status/expired'; +import NeedsReview from '../../../Components/poa/status/needs-review'; +import NotRequired from '../../../Components/poa/status/not-required'; +import PoaStatusCodes from '../../../Components/poa/status/status-codes'; +import ProofOfAddressForm from './proof-of-address-form'; +import Submitted from '../../../Components/poa/status/submitted'; +import Unverified from '../../../Components/poa/status/unverified'; +import Verified from '../../../Components/poa/status/verified'; +import { populateVerificationStatus } from '../Helpers/verification.js'; + +type TAuthenticationStatus = Record< + | 'allow_document_upload' + | 'allow_poi_resubmission' + | 'allow_poa_resubmission' + | 'is_age_verified' + | 'has_poi' + | 'has_submitted_poa' + | 'needs_poa' + | 'needs_poi' + | 'poa_address_mismatch' + | 'resubmit_poa', + boolean +> & { document_status?: DeepRequired['authentication']['document']['status'] }; + +const ProofOfAddressContainer = observer(() => { + const [is_loading, setIsLoading] = React.useState(true); + const [authentication_status, setAuthenticationStatus] = React.useState({ + allow_document_upload: false, + allow_poi_resubmission: false, + allow_poa_resubmission: false, + needs_poi: false, + needs_poa: false, + has_poi: false, + resubmit_poa: false, + has_submitted_poa: false, + document_status: undefined, + is_age_verified: false, + poa_address_mismatch: false, + }); + + const { client, notifications, common } = useStore(); + const { app_routing_history } = common; + const { landing_company_shortcode, has_restricted_mt5_account, is_switching } = client; + const { refreshNotifications } = notifications; + + const is_mx_mlt = landing_company_shortcode === 'iom' || landing_company_shortcode === 'malta'; + + React.useEffect(() => { + if (!is_switching) { + WS.authorized.getAccountStatus().then((response: AccountStatusResponse) => { + const { get_account_status } = response; + if (get_account_status) { + const { + allow_document_upload, + allow_poa_resubmission, + document_status, + has_submitted_poa, + is_age_verified, + needs_poa, + needs_poi, + poa_address_mismatch, + } = populateVerificationStatus(get_account_status); + + setAuthenticationStatus(authentication_status => ({ + ...authentication_status, + allow_document_upload, + allow_poa_resubmission, + document_status, + has_submitted_poa, + is_age_verified, + needs_poa, + needs_poi, + poa_address_mismatch, + })); + setIsLoading(false); + refreshNotifications(); + } + }); + } + }, [is_switching, refreshNotifications]); + + const handleResubmit = () => { + setAuthenticationStatus(authentication_status => ({ ...authentication_status, ...{ resubmit_poa: true } })); + }; + + const onSubmit = (needs_poi: boolean) => { + setAuthenticationStatus(authentication_status => ({ + ...authentication_status, + ...{ has_submitted_poa: true, needs_poi }, + })); + }; + + const { + allow_document_upload, + allow_poa_resubmission, + document_status, + needs_poi, + resubmit_poa, + has_submitted_poa, + is_age_verified, + poa_address_mismatch, + } = authentication_status; + + const from_platform = getPlatformRedirect(app_routing_history); + + const should_show_redirect_btn = Object.keys(platforms).includes(from_platform?.ref ?? ''); + + const redirect_button = should_show_redirect_btn && ( + + ); + + if (is_loading) return ; + if ( + !allow_document_upload || + (!is_age_verified && !allow_poa_resubmission && document_status === 'none' && is_mx_mlt) + ) + return ; + if (has_submitted_poa && !poa_address_mismatch) + return ; + if ( + resubmit_poa || + allow_poa_resubmission || + (has_restricted_mt5_account && + document_status && + ['expired', 'rejected', 'suspected'].includes(document_status)) || + poa_address_mismatch + ) { + return ; + } + + switch (document_status) { + case PoaStatusCodes.none: + return ; + case PoaStatusCodes.pending: + return ; + case PoaStatusCodes.verified: + return ; + case PoaStatusCodes.expired: + return ; + case PoaStatusCodes.rejected: + case PoaStatusCodes.suspected: + return ; + default: + return null; + } +}); + +export default ProofOfAddressContainer; diff --git a/packages/account/src/Sections/Verification/ProofOfAddress/proof-of-address-form.jsx b/packages/account/src/Sections/Verification/ProofOfAddress/proof-of-address-form.jsx deleted file mode 100644 index b47e1d6190e6..000000000000 --- a/packages/account/src/Sections/Verification/ProofOfAddress/proof-of-address-form.jsx +++ /dev/null @@ -1,464 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { - Autocomplete, - Loading, - Button, - Input, - DesktopWrapper, - MobileWrapper, - SelectNative, - FormSubmitErrorMessage, - Text, - useStateCallback, -} from '@deriv/components'; -import { Formik, Field } from 'formik'; -import { localize, Localize } from '@deriv/translations'; -import { - isMobile, - removeEmptyPropertiesFromObject, - validAddress, - validPostCode, - validLetterSymbol, - validLength, - getLocation, - WS, -} from '@deriv/shared'; -import FormFooter from 'Components/form-footer'; -import FormBody from 'Components/form-body'; -import FormBodySection from 'Components/form-body-section'; -import FormSubHeader from 'Components/form-sub-header'; -import LoadErrorMessage from 'Components/load-error-message'; -import LeaveConfirm from 'Components/leave-confirm'; -import FileUploaderContainer from 'Components/file-uploader-container'; -import { observer, useStore } from '@deriv/stores'; - -const validate = (errors, values) => (fn, arr, err_msg) => { - arr.forEach(field => { - const value = values[field]; - if (!fn(value) && !errors[field] && err_msg !== true) errors[field] = err_msg; - }); -}; - -let file_uploader_ref = null; - -const UploaderSideNote = () => ( -
- - - - - - -
-); - -const ProofOfAddressForm = observer(({ is_resubmit, onSubmit }) => { - const { client, notifications } = useStore(); - const { account_settings, fetchResidenceList, fetchStatesList, is_eu, states_list } = client; - const { - addNotificationMessageByKey: addNotificationByKey, - removeNotificationMessage, - removeNotificationByKey, - } = notifications; - const [document_file, setDocumentFile] = React.useState({ files: [], error_message: null }); - const [is_loading, setIsLoading] = React.useState(true); - const [form_values, setFormValues] = useStateCallback({}); - const [api_initial_load_error, setAPIInitialLoadError] = React.useState(null); - const [form_state, setFormState] = useStateCallback({ should_show_form: true }); - - React.useEffect(() => { - fetchResidenceList().then(() => { - Promise.all([fetchStatesList(), WS.wait('get_settings')]).then(() => { - const { citizen, tax_identification_number, tax_residence } = account_settings; - setFormValues( - { - ...account_settings, - ...(is_eu ? { citizen, tax_identification_number, tax_residence } : {}), - }, - () => setIsLoading(false) - ); - }); - }); - }, [account_settings, fetchResidenceList, fetchStatesList, is_eu, setFormValues]); - - const validateFields = values => { - Object.entries(values).forEach(([key, value]) => (values[key] = value.trim())); - - setFormState({ ...form_state, ...{ should_allow_submit: false } }); - const errors = {}; - const validateValues = validate(errors, values); - - const required_fields = ['address_line_1', 'address_city']; - validateValues(val => val, required_fields, localize('This field is required')); - - const address_line_1_validation_result = validAddress(values.address_line_1, { is_required: true }); - if (!address_line_1_validation_result.is_ok) { - errors.address_line_1 = address_line_1_validation_result.message; - } - const address_line_2_validation_result = validAddress(values.address_line_2); - if (!address_line_2_validation_result.is_ok) { - errors.address_line_2 = address_line_2_validation_result.message; - } - - const validation_letter_symbol_message = localize( - 'Only letters, space, hyphen, period, and apostrophe are allowed.' - ); - - if (values.address_city && !validLetterSymbol(values.address_city)) { - errors.address_city = validation_letter_symbol_message; - } - - // only add state/province validation for countries that don't have states list fetched from API - if (values.address_state && !validLetterSymbol(values.address_state) && states_list?.length < 1) { - errors.address_state = validation_letter_symbol_message; - } - - if (values.address_postcode) { - if (!validLength(values.address_postcode, { min: 0, max: 20 })) { - errors.address_postcode = localize('Please enter a {{field_name}} under {{max_number}} characters.', { - field_name: localize('Postal/ZIP code'), - max_number: 20, - interpolation: { escapeValue: false }, - }); - } else if (!validPostCode(values.address_postcode)) { - errors.address_postcode = localize('Only letters, numbers, space, and hyphen are allowed.'); - } - } - - return errors; - }; - - const showForm = bool => { - setFormState({ ...form_state, ...{ should_show_form: bool } }); - }; - - // Settings update is handled here - const onSubmitValues = (values, { setStatus, setSubmitting }) => { - setStatus({ msg: '' }); - setFormState({ ...form_state, ...{ is_btn_loading: true } }); - let settings_values = { ...values }; - - if (values.address_state && states_list.length) { - settings_values.address_state = getLocation(states_list, values.address_state, 'value') || ''; - } - - if (is_eu) { - const { citizen, tax_residence, tax_identification_number } = form_values; - settings_values = removeEmptyPropertiesFromObject({ - ...settings_values, - citizen, - tax_identification_number, - tax_residence, - }); - } - - WS.setSettings(settings_values).then(data => { - if (data.error) { - setStatus({ msg: data.error.message }); - setFormState({ ...form_state, ...{ is_btn_loading: false } }); - setSubmitting(false); - } else { - // force request to update settings cache since settings have been updated - WS.authorized.storage - .getSettings() - .then(({ error, get_settings }) => { - if (error) { - setAPIInitialLoadError(error.message); - setSubmitting(false); - return; - } - const { address_line_1, address_line_2, address_city, address_state, address_postcode } = - get_settings; - - setFormValues( - { - address_line_1, - address_line_2, - address_city, - address_state, - address_postcode, - }, - () => setIsLoading(false) - ); - }) - .then(() => { - // upload files - file_uploader_ref?.current - .upload() - .then(api_response => { - if (api_response.warning) { - setStatus({ msg: api_response.message }); - setFormState({ ...form_state, ...{ is_btn_loading: false } }); - } else { - WS.authorized.storage.getAccountStatus().then(({ error, get_account_status }) => { - if (error) { - setAPIInitialLoadError(error.message); - setSubmitting(false); - return; - } - setFormState( - { ...form_state, ...{ is_submit_success: true, is_btn_loading: false } }, - () => { - const { identity, needs_verification } = - get_account_status.authentication; - const has_poi = !(identity && identity.status === 'none'); - // TODO: clean all of this up by simplifying the manually toggled notifications functions - const needs_poi = - needs_verification.length && - needs_verification.includes('identity'); - onSubmit({ has_poi }); - removeNotificationMessage({ key: 'authenticate' }); - removeNotificationByKey({ key: 'authenticate' }); - removeNotificationMessage({ key: 'needs_poa' }); - removeNotificationByKey({ key: 'needs_poa' }); - removeNotificationMessage({ key: 'poa_expired' }); - removeNotificationByKey({ key: 'poa_expired' }); - if (needs_poi) { - addNotificationByKey('needs_poi'); - } - } - ); - }); - } - }) - .catch(error => { - setStatus({ msg: error.message }); - setFormState({ ...form_state, ...{ is_btn_loading: false } }); - }) - .then(() => { - setSubmitting(false); - setFormState({ ...form_state, ...{ is_btn_loading: false } }); - }); - }); - } - }); - }; - - const { address_line_1, address_line_2, address_city, address_state, address_postcode } = form_values; - - const form_initial_values = { - address_line_1, - address_line_2, - address_city, - address_state, - address_postcode, - }; - - if (api_initial_load_error) { - return ; - } - if (is_loading) return ; - const mobile_scroll_offset = status && status.msg ? '200px' : '154px'; - - if (form_initial_values.address_state) { - form_initial_values.address_state = states_list.length - ? getLocation(states_list, form_initial_values.address_state, 'text') - : form_initial_values.address_state; - } else { - form_initial_values.address_state = ''; - } - - return ( - - {({ - values, - errors, - status, - touched, - handleChange, - handleBlur, - handleSubmit, - isSubmitting, - setFieldValue, - }) => ( - <> - - {form_state.should_show_form && ( - - - {is_resubmit && ( - - {localize( - 'We were unable to verify your address with the details you provided. Please check and resubmit or choose a different document type.' - )} - - )} - - -
-
-
- -
-
- -
-
- -
-
- {states_list.length ? ( - - - - {({ field }) => ( - - setFieldValue( - 'address_state', - value ? text : '', - true - ) - } - /> - )} - - - - - setFieldValue('address_state', e.target.value, true) - } - /> - - - ) : ( - - )} -
-
- -
-
-
-
- - - }> - (file_uploader_ref = ref)} - onFileDrop={df => - setDocumentFile({ files: df.files, error_message: df.error_message }) - } - getSocket={WS.getSocket} - /> - -
- - {status && status.msg && } -
)} ); -}); +}; export default withRouter(ProofOfIdentity); diff --git a/packages/account/src/Sections/Verification/ProofOfOwnership/file-uploader.jsx b/packages/account/src/Sections/Verification/ProofOfOwnership/file-uploader.jsx index eda175ad4938..d82e68c5d713 100644 --- a/packages/account/src/Sections/Verification/ProofOfOwnership/file-uploader.jsx +++ b/packages/account/src/Sections/Verification/ProofOfOwnership/file-uploader.jsx @@ -1,7 +1,7 @@ import React from 'react'; +import classNames from 'classnames'; import { localize } from '@deriv/translations'; import { Button, Input, Icon } from '@deriv/components'; -import classNames from 'classnames'; import { compressImageFiles } from '@deriv/shared'; import PropTypes from 'prop-types'; @@ -44,7 +44,7 @@ const FileUploader = ({ await updateErrors(index, item_index, sub_index); }; return ( -
+
; export type TIDVVerificationResponse = IdentityVerificationAddDocumentResponse & { error: { message: string } }; -export type TDocumentList = { +export type TVerificationStatus = Readonly< + Record<'none' | 'pending' | 'rejected' | 'verified' | 'expired' | 'suspected', string> +>; + +export type TDocument = { id: string; text: string; value?: string; @@ -171,8 +176,8 @@ export type TDocumentList = { }; }; -type TFormProps = { - document_type: TDocumentList; +export type TIDVFormValues = { + document_type: TDocument; document_number: string; document_additional?: string; error_message?: string; @@ -184,17 +189,13 @@ export type TIDVForm = { class_name?: string; can_skip_document_verification: boolean; } & Partial & - FormikProps; + FormikProps; -export type TVerificationStatus = Readonly< - Record<'none' | 'pending' | 'rejected' | 'verified' | 'expired' | 'suspected', string> ->; - -export type TIDVFormValues = { - document_type: TDocumentList; - document_number: string; - document_additional?: string; - error_message?: string; +export type TServerError = { + code: string; + message: string; + details?: { [key: string]: string }; + fields?: string[]; }; export type TCFDPlatform = typeof CFD_PLATFORMS[keyof typeof CFD_PLATFORMS]; diff --git a/packages/account/src/Types/context.type.ts b/packages/account/src/Types/context.type.ts index cf9643605332..0794965c5e2f 100644 --- a/packages/account/src/Types/context.type.ts +++ b/packages/account/src/Types/context.type.ts @@ -1,4 +1,4 @@ -import { TToken } from './common-prop.type'; +import { TToken } from './common.type'; export type TApiContext = { api_tokens: NonNullable; diff --git a/packages/account/src/Types/index.ts b/packages/account/src/Types/index.ts index 07ac06fc0417..39a4ad4a03b9 100644 --- a/packages/account/src/Types/index.ts +++ b/packages/account/src/Types/index.ts @@ -1,2 +1,2 @@ -export * from './common-prop.type'; +export * from './common.type'; export * from './context.type'; diff --git a/packages/api/src/hooks/index.ts b/packages/api/src/hooks/index.ts index 059da047168c..e69f873032f7 100644 --- a/packages/api/src/hooks/index.ts +++ b/packages/api/src/hooks/index.ts @@ -11,12 +11,19 @@ export { default as useCloseDerivAccount } from './useCloseDerivAccount'; export { default as useCurrencyConfig } from './useCurrencyConfig'; export { default as useGetAccountStatus } from './useGetAccountStatus'; export { default as useLandingCompany } from './useLandingCompany'; -export { default as useMT5LoginList } from './useMT5LoginList'; +export { default as useMT5AccountsList } from './useMT5AccountsList'; export { default as useSettings } from './useSettings'; export { default as useTradingAccountsList } from './useTradingAccountsList'; export { default as useTradingPlatformAccounts } from './useTradingPlatformAccounts'; export { default as useTradingPlatformAvailableAccounts } from './useTradingPlatformAvailableAccounts'; export { default as useWalletAccountsList } from './useWalletAccountsList'; +export { default as useTradingPlatformInvestorPasswordChange } from './useTradingPlatformInvestorPasswordChange'; export { default as useCreateMT5Account } from './useCreateMT5Account'; export { default as useCreateOtherCFDAccount } from './useCreateOtherCFDAccount'; export { default as useApiToken } from './useApiToken'; +export { default as useTradingPlatformPasswordChange } from './useTradingPlatformPasswordChange'; +export { default as useTradingPlatformInvestorPasswordReset } from './useTradingPlatformInvestorPasswordReset'; +export { default as useDxtradeAccountsList } from './useDxtradeAccountsList'; +export { default as useDerivezAccountsList } from './useDerivezAccountsList'; +export { default as useCFDAccountsList } from './useCFDAccountsList'; +export { default as useCtraderAccountsList } from './useCtraderAccountsList'; diff --git a/packages/api/src/hooks/useCFDAccountsList.ts b/packages/api/src/hooks/useCFDAccountsList.ts new file mode 100644 index 000000000000..0125571fb772 --- /dev/null +++ b/packages/api/src/hooks/useCFDAccountsList.ts @@ -0,0 +1,31 @@ +import { useMemo } from 'react'; +import useMT5AccountsList from './useMT5AccountsList'; +import useDxtradeAccountsList from './useDxtradeAccountsList'; +import useDerivezAccountsList from './useDerivezAccountsList'; +import useCtraderAccountsList from './useCtraderAccountsList'; + +/** A custom hook that gets the list all created CFD accounts of the user. */ +const useCFDAccountsList = () => { + const { data: mt5_accounts } = useMT5AccountsList(); + const { data: dxtrade_accounts } = useDxtradeAccountsList(); + const { data: derivez_accounts } = useDerivezAccountsList(); + const { data: ctrader_accounts } = useCtraderAccountsList(); + + const data = useMemo(() => { + if (!mt5_accounts || !dxtrade_accounts || !derivez_accounts || !ctrader_accounts) return; + + return { + mt5_accounts: mt5_accounts || [], + dxtrade_accounts: dxtrade_accounts || [], + derivez_accounts: derivez_accounts || [], + ctrader_accounts: ctrader_accounts || [], + }; + }, [mt5_accounts, dxtrade_accounts, derivez_accounts, ctrader_accounts]); + + return { + /** The list of created MT5 and Non-MT5 accounts */ + data, + }; +}; + +export default useCFDAccountsList; diff --git a/packages/api/src/hooks/useCtraderAccountsList.ts b/packages/api/src/hooks/useCtraderAccountsList.ts new file mode 100644 index 000000000000..38dc79e07fb0 --- /dev/null +++ b/packages/api/src/hooks/useCtraderAccountsList.ts @@ -0,0 +1,26 @@ +import { useMemo } from 'react'; +import useFetch from '../useFetch'; + +/** A custom hook that gets the list of created cTrader accounts. */ +const useCtraderAccountsList = () => { + const { data: ctrader_accounts } = useFetch('trading_platform_accounts', { + payload: { platform: 'ctrader' }, + }); + + /** Adding neccesary properties to cTrader accounts */ + const modified_ctrader_accounts = useMemo( + () => + ctrader_accounts?.trading_platform_accounts?.map(account => ({ + ...account, + loginid: account.account_id, + })), + [ctrader_accounts?.trading_platform_accounts] + ); + + return { + /** List of all created cTrader accounts */ + data: modified_ctrader_accounts, + }; +}; + +export default useCtraderAccountsList; diff --git a/packages/api/src/hooks/useDerivezAccountsList.ts b/packages/api/src/hooks/useDerivezAccountsList.ts new file mode 100644 index 000000000000..57e9b37fe27e --- /dev/null +++ b/packages/api/src/hooks/useDerivezAccountsList.ts @@ -0,0 +1,26 @@ +import { useMemo } from 'react'; +import useFetch from '../useFetch'; + +/** A custom hook that gets the list of created DerivEz accounts. */ +const useDerivezAccountsList = () => { + const { data: derivez_accounts } = useFetch('trading_platform_accounts', { + payload: { platform: 'derivez' }, + }); + + /** Adding neccesary properties to DerivEz accounts */ + const modified_derivez_accounts = useMemo( + () => + derivez_accounts?.trading_platform_accounts?.map(account => ({ + ...account, + loginid: account.account_id, + })), + [derivez_accounts?.trading_platform_accounts] + ); + + return { + /** List of all created DerivEz accounts */ + data: modified_derivez_accounts, + }; +}; + +export default useDerivezAccountsList; diff --git a/packages/api/src/hooks/useDxtradeAccountsList.ts b/packages/api/src/hooks/useDxtradeAccountsList.ts new file mode 100644 index 000000000000..4da5d352a026 --- /dev/null +++ b/packages/api/src/hooks/useDxtradeAccountsList.ts @@ -0,0 +1,26 @@ +import { useMemo } from 'react'; +import useFetch from '../useFetch'; + +/** A custom hook that gets the list of created Deriv X accounts. */ +const useDxtradeAccountsList = () => { + const { data: dxtrade_accounts } = useFetch('trading_platform_accounts', { + payload: { platform: 'dxtrade' }, + }); + + /** Adding neccesary properties to Deriv X accounts */ + const modified_dxtrade_accounts = useMemo( + () => + dxtrade_accounts?.trading_platform_accounts?.map(account => ({ + ...account, + loginid: account.account_id, + })), + [dxtrade_accounts?.trading_platform_accounts] + ); + + return { + /** List of all created Deriv X accounts */ + data: modified_dxtrade_accounts, + }; +}; + +export default useDxtradeAccountsList; diff --git a/packages/api/src/hooks/useMT5LoginList.ts b/packages/api/src/hooks/useMT5AccountsList.ts similarity index 92% rename from packages/api/src/hooks/useMT5LoginList.ts rename to packages/api/src/hooks/useMT5AccountsList.ts index 3ec8b5626d6c..0c289221bff5 100644 --- a/packages/api/src/hooks/useMT5LoginList.ts +++ b/packages/api/src/hooks/useMT5AccountsList.ts @@ -3,7 +3,7 @@ import useActiveWalletAccount from './useActiveWalletAccount'; import useFetch from '../useFetch'; /** A custom hook that gets the list created MT5 accounts of the user. */ -const useMT5LoginList = () => { +const useMT5AccountsList = () => { const { data: wallet } = useActiveWalletAccount(); const { data: mt5_accounts, ...mt5_accounts_rest } = useFetch('mt5_login_list'); @@ -24,6 +24,7 @@ const useMT5LoginList = () => { ...account, ...getAccountInfo(account.login), loginid: account.login, + platform: 'mt5', })); }, [mt5_accounts?.mt5_login_list, wallet?.linked_to]); @@ -34,4 +35,4 @@ const useMT5LoginList = () => { }; }; -export default useMT5LoginList; +export default useMT5AccountsList; diff --git a/packages/api/src/hooks/useTradingPlatformInvestorPasswordChange.ts b/packages/api/src/hooks/useTradingPlatformInvestorPasswordChange.ts new file mode 100644 index 000000000000..6a3750780df0 --- /dev/null +++ b/packages/api/src/hooks/useTradingPlatformInvestorPasswordChange.ts @@ -0,0 +1,21 @@ +import { useCallback } from 'react'; +import useRequest from '../useRequest'; + +type TPayload = Parameters< + ReturnType>['mutate'] +>[0]['payload']; + +/** A custom hook that change the Trading Platform Investor Password. */ +const useTradingPlatformInvestorPasswordChange = () => { + const { mutate: _mutate, ...rest } = useRequest('trading_platform_investor_password_change'); + + const mutate = useCallback((payload: TPayload) => _mutate({ payload }), [_mutate]); + + return { + /** The mutation function that accepts a payload and sends it to the server */ + mutate, + ...rest, + }; +}; + +export default useTradingPlatformInvestorPasswordChange; diff --git a/packages/api/src/hooks/useTradingPlatformInvestorPasswordReset.ts b/packages/api/src/hooks/useTradingPlatformInvestorPasswordReset.ts new file mode 100644 index 000000000000..14c2ce27a7b9 --- /dev/null +++ b/packages/api/src/hooks/useTradingPlatformInvestorPasswordReset.ts @@ -0,0 +1,21 @@ +import { useCallback } from 'react'; +import useRequest from '../useRequest'; + +type TPayload = Parameters< + ReturnType>['mutate'] +>[0]['payload']; + +/** A custom hook that reset the Trading Platform Investor Password. */ +const useTradingPlatformInvestorPasswordReset = () => { + const { mutate: _mutate, ...rest } = useRequest('trading_platform_investor_password_reset'); + + const mutate = useCallback((payload: TPayload) => _mutate({ payload }), [_mutate]); + + return { + /** The mutation function that accepts a payload and sends it to the server */ + mutate, + ...rest, + }; +}; + +export default useTradingPlatformInvestorPasswordReset; diff --git a/packages/api/src/hooks/useTradingPlatformPasswordChange.ts b/packages/api/src/hooks/useTradingPlatformPasswordChange.ts new file mode 100644 index 000000000000..89c48b6bae04 --- /dev/null +++ b/packages/api/src/hooks/useTradingPlatformPasswordChange.ts @@ -0,0 +1,19 @@ +import { useCallback } from 'react'; +import useRequest from '../useRequest'; + +type TPayload = Parameters>['mutate']>[0]['payload']; + +/** A custom hook that change the Trading Platform Password. */ +const useTradingPlatformPasswordChange = () => { + const { mutate: _mutate, ...rest } = useRequest('trading_platform_password_change'); + + const mutate = useCallback((payload: TPayload) => _mutate({ payload }), [_mutate]); + + return { + /** The mutation function that accepts a payload and sends it to the server */ + mutate, + ...rest, + }; +}; + +export default useTradingPlatformPasswordChange; diff --git a/packages/api/src/hooks/useVerifyEmail.ts b/packages/api/src/hooks/useVerifyEmail.ts new file mode 100644 index 000000000000..2106b86a056d --- /dev/null +++ b/packages/api/src/hooks/useVerifyEmail.ts @@ -0,0 +1,19 @@ +import { useCallback } from 'react'; +import { useRequest } from '@deriv/api'; + +type TPayload = Parameters>['mutate']>[0]['payload']; + +/** A custom hook for verifying email address */ +const useVerifyEmail = () => { + const { mutate: _mutate, ...rest } = useRequest('verify_email'); + + const mutate = useCallback((payload: TPayload) => _mutate({ payload }), [_mutate]); + + return { + /** The mutation function that accepts a payload and sends it to the server */ + mutate, + ...rest, + }; +}; + +export default useVerifyEmail; diff --git a/packages/api/types.ts b/packages/api/types.ts index 7e6bbf35d547..7269937a918f 100644 --- a/packages/api/types.ts +++ b/packages/api/types.ts @@ -229,6 +229,106 @@ import type { import type { useMutation, useQuery } from '@tanstack/react-query'; type TPrivateSocketEndpoints = { + trading_platform_investor_password_reset: { + request: { + /** + * Must be `1` + */ + trading_platform_investor_password_reset: 1; + /** + * Trading account ID. + */ + account_id: string; + /** + * New password of the account. For validation (Accepts any printable ASCII character. Must be within 8-25 characters, and include numbers, lowercase and uppercase letters. Must not be the same as the user's email address). + */ + new_password: string; + /** + * Name of trading platform. + */ + platform: 'mt5'; + /** + * Email verification code (received from a `verify_email` call, which must be done first) + */ + verification_code: string; + /** + * [Optional] Used to pass data through the websocket, which may be retrieved via the `echo_req` output field. Maximum size is 3500 bytes. + */ + passthrough?: { + [k: string]: unknown; + }; + /** + * [Optional] Used to map request to response. + */ + req_id?: number; + }; + response: { + trading_platform_investor_password_reset?: 0 | 1; + }; + /** + * Echo of the request made. + */ + echo_req: { + [k: string]: unknown; + }; + /** + * Action name of the request made. + */ + msg_type: 'trading_platform_investor_password_reset'; + /** + * Optional field sent in request to map to response, present only when request contains `req_id`. + */ + req_id?: number; + [k: string]: unknown; + }; + trading_platform_password_change: { + request: { + /** + * Must be `1` + */ + trading_platform_password_change: 1; + /** + * New trading password. Accepts any printable ASCII character. Must be within 8-25 characters, and include numbers, lowercase and uppercase letters. Must not be the same as the user's email address. + */ + new_password: string; + /** + * Old password for validation. Must be empty if a password has not been set yet. + */ + old_password?: string; + /** + * Name of trading platform. + */ + platform: 'dxtrade' | 'mt5'; + /** + * [Optional] Used to pass data through the websocket, which may be retrieved via the `echo_req` output field. Maximum size is 3500 bytes. + */ + passthrough?: { + [k: string]: unknown; + }; + /** + * [Optional] Used to map request to response. + */ + req_id?: number; + }; + response: { + trading_platform_password_change?: 0 | 1; + }; + /** + * Echo of the request made. + */ + echo_req: { + [k: string]: unknown; + }; + /** + * Action name of the request made. + */ + msg_type: 'trading_platform_password_change'; + /** + * Optional field sent in request to map to response, present only when request contains `req_id`. + */ + req_id?: number; + [k: string]: unknown; + }; trading_platform_new_account: { request: { /** @@ -454,7 +554,7 @@ type TPrivateSocketEndpoints = { /** * Trading platform name */ - platform: 'dxtrade' | 'mt5' | 'derivez'; + platform: 'dxtrade' | 'mt5' | 'derivez' | 'ctrader'; /** * [Optional] Used to pass data through the websocket, which may be retrieved via the `echo_req` output field. */ @@ -594,7 +694,7 @@ type TPrivateSocketEndpoints = { /** * Name of trading platform. */ - platform?: 'dxtrade' | 'mt5'; + platform?: 'dxtrade' | 'mt5' | 'ctrader'; /** * Trade server name of the MT5 account. */ @@ -901,6 +1001,58 @@ type TPrivateSocketEndpoints = { [k: string]: unknown; }; }; + trading_platform_investor_password_change: { + request: { + /** + * Must be `1` + */ + trading_platform_investor_password_change: 1; + /** + * Trading account ID. + */ + account_id: string; + /** + * New investor password. Accepts any printable ASCII character. Must be within 8-25 characters, and include numbers, lowercase and uppercase letters. Must not be the same as the user's email address. + */ + new_password: string; + /** + * Old investor password for validation (non-empty string, accepts any printable ASCII character) + */ + old_password: string; + /** + * Name of trading platform. + */ + platform: 'mt5'; + /** + * [Optional] Used to pass data through the websocket, which may be retrieved via the `echo_req` output field. Maximum size is 3500 bytes. + */ + passthrough?: { + [k: string]: unknown; + }; + /** + * [Optional] Used to map request to response. + */ + req_id?: number; + }; + response: { + trading_platform_password_change?: 0 | 1; + }; + /** + * Echo of the request made. + */ + echo_req: { + [k: string]: unknown; + }; + /** + * Action name of the request made. + */ + msg_type: 'trading_platform_investor_password_change'; + /** + * Optional field sent in request to map to response, present only when request contains `req_id`. + */ + req_id?: number; + [k: string]: unknown; + }; }; type TSocketEndpoints = { diff --git a/packages/appstore/src/assets/svgs/trading-platform/branding/ic-branding-ctrader.svg b/packages/appstore/src/assets/svgs/trading-platform/branding/ic-branding-ctrader.svg new file mode 100644 index 000000000000..265b5c52d55b --- /dev/null +++ b/packages/appstore/src/assets/svgs/trading-platform/branding/ic-branding-ctrader.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/appstore/src/assets/svgs/trading-platform/index.tsx b/packages/appstore/src/assets/svgs/trading-platform/index.tsx index 6a4d77989224..ff8f813b0e3d 100644 --- a/packages/appstore/src/assets/svgs/trading-platform/index.tsx +++ b/packages/appstore/src/assets/svgs/trading-platform/index.tsx @@ -2,6 +2,7 @@ import React from 'react'; import BinaryBot from 'Assets/svgs/trading-platform/branding/ic-branding-binarybot-dashboard.svg'; import BinaryBotBlue from 'Assets/svgs/trading-platform/ic-appstore-binarybot-blue.svg'; import CFDs from 'Assets/svgs/trading-platform/branding/ic-branding-mt5-cfds.svg'; +import CTrader from 'Assets/svgs/trading-platform/branding/ic-branding-ctrader.svg'; import DBot from 'Assets/svgs/trading-platform/branding/ic-branding-dbot-dashboard.svg'; import Demo from 'Assets/svgs/trading-platform/ic-brand-demo.svg'; import Derived from 'Assets/svgs/trading-platform/branding/ic-branding-mt5-derived-dashboard.svg'; @@ -24,6 +25,7 @@ export const PlatformIcons = { BinaryBot, BinaryBotBlue, CFDs, + CTrader, DBot, Demo, Derived, diff --git a/packages/appstore/src/components/cfds-listing/cfds-listing.scss b/packages/appstore/src/components/cfds-listing/cfds-listing.scss index d1f5df1b1f0d..e4f61d3270bf 100644 --- a/packages/appstore/src/components/cfds-listing/cfds-listing.scss +++ b/packages/appstore/src/components/cfds-listing/cfds-listing.scss @@ -396,6 +396,15 @@ color: var(--text-less-prominent); line-height: 1.45; } + &--tag { + background-color: $color-blue; + color: var(--text-colored-background); + border-radius: 0.2rem; + padding: 0 0.4rem; + font-weight: bold; + font-size: 1.3rem; + } + &__mobile { &-title { text-align: left; @@ -428,7 +437,6 @@ &__maintenance { display: flex; padding-top: 1rem; - align-items: center; &-icon { padding-right: 0.5rem; @@ -439,13 +447,18 @@ } } - &__dxtrade-button { + &__dxtrade-button, + &__platform-button { display: flex; background: var(--brand-dark-grey); align-items: center; text-decoration: none; border-radius: $BORDER_RADIUS; + &-icon { + margin-left: 0.6rem; + } + &-text { padding: 0.5rem; @@ -454,9 +467,6 @@ white-space: nowrap; } } - &-icon { - margin-left: 0.6rem; - } } &__specs { padding: 1.6rem; @@ -543,6 +553,30 @@ border: 1px solid var(--border-normal-2); color: var(--text-less-prominent); + &:hover { + background-color: var(--button-secondary-hover); + } + } + } + &--windows { + display: flex; + gap: 1.6rem; + align-items: center; + padding: 1.6rem 2.4rem; + border-bottom: 1px solid var(--border-disabled); + &-item { + flex-grow: 1; + } + &-link { + align-items: center; + display: flex; + justify-content: center; + text-decoration: none; + @include typeface(--paragraph-center-bold-black, none); + background-color: transparent; + border: 1px solid var(--border-normal-2); + color: var(--text-less-prominent); + &:hover { background-color: var(--button-secondary-hover); } @@ -969,6 +1003,10 @@ .dc-autocomplete { margin-bottom: 3.6rem; } + + .details-form__description { + margin-inline: 1.6rem; + } } } @@ -999,160 +1037,6 @@ } } -.account-poa { - &__upload { - &-remove-btn { - position: absolute; - width: 16px; - height: 16px; - top: 8px; - right: 8px; - cursor: pointer; - transition: transform 0.25s linear; - - &:hover { - transform: scale(1.25, 1.25); - } - &--error { - circle { - fill: var(--status-danger); - } - } - &-container { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - } - } - } -} - -.cfd-proof-of-address { - height: 100%; - & .details-form { - display: grid; - grid-template-rows: minmax(10rem, 1fr) 7.2rem; - height: 100%; - .dc-modal-footer { - border-top: 1px solid var(--general-active); - } - } - &__file-upload { - padding-top: 0.8rem; - position: relative; - - .account-poa { - &__upload { - &-section { - margin-top: 0; - display: flex; - } - &-file { - width: 100%; - flex: 1; - height: 24rem; - position: relative; - margin: 0; - - .dc-file-dropzone { - border: 1px dashed var(--border-normal); - max-width: 40rem; - - &__message-subtitle { - font-size: unset; - font-weight: unset; - } - } - @include mobile { - flex: unset; - } - } - &-list { - display: unset; - - .account-poa__upload-box { - display: flex; - flex-direction: unset; - flex: unset; - align-items: center; - justify-content: flex-start; - margin: 0 1.6rem 0.8rem 0; - border: none; - border-radius: 0; - padding: 0; - text-align: unset; - } - } - &-icon { - width: 2.5rem; - margin-bottom: 0; - } - &-item { - min-width: 23.8rem; - width: 100%; - margin-left: 1rem; - font-size: var(--text-size-xxs); - line-height: 1.5; - color: var(--text-prominent); - padding: 0; - } - } - } - } - &__field-area { - max-width: 556px; - margin: 0 auto; - padding: 3rem 0; - - & .dc-dropdown__display-placeholder-text, - & .dc-input__label, - & .dc-dropdown__display { - background: var(--general-main-2); - } - @include mobile { - overflow: auto; - } - } - &__inline-fields { - display: grid; - grid-template-columns: 1fr 1fr 1fr; - grid-gap: 1rem; - - .dc-dropdown-container { - margin-top: 0; - } - } - @include mobile { - .dc-input__label { - background: var(--general-section-2); - } - &__field-area { - padding: 0 1.6rem 3.2rem; - max-height: calc(100% - 0.8rem); - } - &__inline-fields { - display: flex; - flex-direction: column; - - .dc-select-native { - margin-bottom: 3.2rem; - } - } - &__file-upload { - .dc-field--error { - top: 50%; - left: 0; - right: unset; - } - .account-poa__upload-section { - flex-direction: column; - } - } - } -} - .cfd-proof-of-identity { height: 100%; overflow: auto; diff --git a/packages/appstore/src/components/cfds-listing/index.tsx b/packages/appstore/src/components/cfds-listing/index.tsx index 49c3fe1057bc..9ce334e15dd4 100644 --- a/packages/appstore/src/components/cfds-listing/index.tsx +++ b/packages/appstore/src/components/cfds-listing/index.tsx @@ -31,7 +31,7 @@ const CFDsListing = observer(() => { } = useStore(); const { available_dxtrade_accounts, - available_derivez_accounts, + available_ctrader_accounts, combined_cfd_mt5_accounts, selected_region, has_any_real_account, @@ -72,7 +72,7 @@ const CFDsListing = observer(() => { const getAuthStatus = (status_list: boolean[]) => status_list.some(status => status); - const getMT5AccountAuthStatus = (current_acc_status: string, jurisdiction?: string) => { + const getMT5AccountAuthStatus = (current_acc_status?: string | null, jurisdiction?: string) => { if (jurisdiction) { switch (jurisdiction) { case Jurisdiction.BVI: { @@ -255,6 +255,80 @@ const CFDsListing = observer(() => { ) : ( )} + + {!is_eu_user && !CFDs_restricted_countries && !financial_restricted_countries && !is_real && ( +
+
+
+ )} + + {!is_eu_user && !CFDs_restricted_countries && !financial_restricted_countries && !is_real && ( +
+ {localize('Deriv cTrader')} +
+ )} + + {is_landing_company_loaded && !is_real + ? available_ctrader_accounts.map((account: AvailableAccount) => { + const existing_accounts = getExistingAccounts(account.platform, account.market_type); + const has_existing_accounts = existing_accounts.length > 0; + return has_existing_accounts ? ( + existing_accounts.map((existing_account: TDetailsOfEachMT5Loginid) => ( + ) => { + const button_name = e?.currentTarget?.name; + if (button_name === 'transfer-btn') { + toggleAccountTransferModal(); + setSelectedAccount(existing_account); + } else if (button_name === 'topup-btn') { + showTopUpModal(existing_account); + setAppstorePlatform(account.platform); + } else { + startTrade(account.platform, existing_account); + } + }} + /> + )) + ) : ( + { + if ((has_no_real_account || no_CR_account) && is_real) { + openDerivRealAccountNeededModal(); + } else { + setAccountType({ + category: selected_account_type, + type: account.market_type, + }); + setAppstorePlatform(account.platform); + getAccount(); + } + }} + key={`trading_app_card_${account.name}`} + /> + ); + }) + : !is_real && } + {!is_eu_user && !CFDs_restricted_countries && !financial_restricted_countries && (
@@ -330,68 +404,6 @@ const CFDsListing = observer(() => { ) : ( )} - - {/* TODO: remove is_real flag to unblock the flow for derivez real account creation */} - {/* {is_landing_company_loaded && !is_real - ? available_derivez_accounts?.map((account: AvailableAccount) => { - const existing_accounts = getExistingAccounts(account.platform, account.market_type); - const has_existing_accounts = existing_accounts.length > 0; - return has_existing_accounts ? ( - existing_accounts.map((existing_account: TDetailsOfEachMT5Loginid) => ( - ) => { - const button_name = e?.currentTarget?.name; - if (button_name === 'transfer-btn') { - toggleAccountTransferModal(); - setSelectedAccount(existing_account); - } else if (button_name === 'topup-btn') { - showTopUpModal(existing_account); - setAppstorePlatform(account.platform); - } else { - startTrade(account.platform, existing_account); - } - }} - /> - )) - ) : ( - { - if ((has_no_real_account || no_CR_account) && is_real) { - openDerivRealAccountNeededModal(); - } else { - setAccountType({ - category: selected_account_type, - type: account.market_type, - }); - setAppstorePlatform(account.platform); - getAccount(); - } - }} - key={`trading_app_card_${account.name}`} - /> - ); - }) - : !is_real && } */} ); }); diff --git a/packages/appstore/src/components/containers/trading-app-card.tsx b/packages/appstore/src/components/containers/trading-app-card.tsx index 4c5df2474ecd..46a3a867a257 100644 --- a/packages/appstore/src/components/containers/trading-app-card.tsx +++ b/packages/appstore/src/components/containers/trading-app-card.tsx @@ -53,6 +53,15 @@ const TradingAppCard = ({ link_to: '', }; + const appDescription = () => { + if ( + platform !== CFD_PLATFORMS.CTRADER || + (platform === CFD_PLATFORMS.CTRADER && action_type !== 'multi-action') + ) { + return app_desc; + } + }; + const { text: badge_text, icon: badge_icon } = getStatusBadgeConfig( mt5_acc_auth_status, openFailedVerificationModal, @@ -125,7 +134,7 @@ const TradingAppCard = ({ {!is_real && !sub_title && !is_deriv_platform ? `${name} ${localize('Demo')}` : name} - {app_desc} + {appDescription()} {mt5_acc_auth_status && ( { const cfd_account_manager = screen.getAllByTestId('cfd_account_manager'); expect(trading_app_card.length).not.toBeGreaterThan(1); expect(trading_app_card[0]).toHaveTextContent('Deriv Trader'); - expect(cfd_account_manager.length).not.toBeGreaterThan(2); + expect(cfd_account_manager.length).not.toBeGreaterThan(3); expect(cfd_account_manager[0]).toHaveTextContent('Deriv account'); expect(cfd_account_manager[1]).toHaveTextContent('Financial'); }); diff --git a/packages/appstore/src/components/onboarding-new/static-cfd-account-manager.tsx b/packages/appstore/src/components/onboarding-new/static-cfd-account-manager.tsx index 08368da76b48..2b97506b04e6 100644 --- a/packages/appstore/src/components/onboarding-new/static-cfd-account-manager.tsx +++ b/packages/appstore/src/components/onboarding-new/static-cfd-account-manager.tsx @@ -138,6 +138,21 @@ const StaticCFDAccountManager = ({ })} /> )} + + { + //TODO: move all of these to a reusable component + } + + {platform === CFD_PLATFORMS.CTRADER && ( + + )} {platform === 'options' && ( + {!is_eu_user && !CFDs_restricted_countries && !financial_restricted_countries && ( + + +
+ + {localize('Deriv cTrader')} + +
+
+ )} + + {!is_eu_user && !CFDs_restricted_countries && !financial_restricted_countries && ( +
+ +
+ )} + {!is_eu_user && !CFDs_restricted_countries && !financial_restricted_countries && ( diff --git a/packages/appstore/src/types/common.types.ts b/packages/appstore/src/types/common.types.ts index d0ed899ca233..846ee2f212c4 100644 --- a/packages/appstore/src/types/common.types.ts +++ b/packages/appstore/src/types/common.types.ts @@ -12,7 +12,7 @@ export type RequiredAndNotNull = { export type TRegionAvailability = 'Non-EU' | 'EU' | 'All'; export type TAccountCategory = 'real' | 'demo'; -export type TPlatform = 'dxtrade' | 'mt5' | 'trader' | 'dbot' | 'smarttrader' | 'bbot' | 'go' | 'derivez'; +export type TPlatform = 'dxtrade' | 'mt5' | 'trader' | 'dbot' | 'smarttrader' | 'bbot' | 'go' | 'derivez' | 'ctrader'; export type TBrandData = { name: string; icon?: string; @@ -53,15 +53,15 @@ export type TJurisdictionData = Record< >; export type TDetailsOfEachMT5Loginid = DetailsOfEachMT5Loginid & { + account_id?: string; display_login?: string; - landing_company_short?: string; short_code_and_region?: string; mt5_acc_auth_status?: string | null; selected_mt5_jurisdiction?: TOpenAccountTransferMeta & TJurisdictionData & { platform?: string; }; - + platform?: TPlatform; openFailedVerificationModal?: (from_account: string) => void; }; @@ -130,7 +130,7 @@ export interface AvailableAccount { description?: string; is_visible?: boolean; is_disabled?: boolean; - platform?: string; + platform?: TPlatform; market_type?: 'all' | 'financial' | 'synthetic'; icon: keyof typeof PlatformIcons; availability: RegionAvailability; diff --git a/packages/appstore/webpack.config.js b/packages/appstore/webpack.config.js index 69e9bbcafea4..2326f6fd28df 100644 --- a/packages/appstore/webpack.config.js +++ b/packages/appstore/webpack.config.js @@ -131,8 +131,12 @@ module.exports = function (env) { loader: 'sass-resources-loader', options: { // Provide path to the file with resources - // eslint-disable-next-line global-require, import/no-dynamic-require - resources: require('@deriv/shared/src/styles/index.js'), + resources: [ + // eslint-disable-next-line global-require, import/no-dynamic-require + ...require('@deriv/shared/src/styles/index.js'), + // eslint-disable-next-line global-require, import/no-dynamic-require + ...require('@deriv/wallets/src/styles/index.js'), + ], }, }, ], diff --git a/packages/bot-web-ui/src/components/dashboard/bot-builder/bot-builder.tsx b/packages/bot-web-ui/src/components/dashboard/bot-builder/bot-builder.tsx index 382a156b57aa..0489762c8228 100644 --- a/packages/bot-web-ui/src/components/dashboard/bot-builder/bot-builder.tsx +++ b/packages/bot-web-ui/src/components/dashboard/bot-builder/bot-builder.tsx @@ -5,17 +5,15 @@ import { observer } from '@deriv/stores'; import { useDBotStore } from '../../../stores/useDBotStore'; import LoadModal from '../../load-modal'; import SaveModal from '../dashboard-component/load-bot-preview/save-modal'; -import { BOT_BUILDER_TOUR } from '../joyride-config'; +import DesktopTours from '../dbot-tours/desktop-tours/desktop-tours'; +import MobileTours from '../dbot-tours/mobile-tours/mobile-tours'; import QuickStrategy from '../quick-strategy'; -import ReactJoyrideWrapper from '../react-joyride-wrapper'; -import TourSlider from '../tour-slider'; import WorkspaceWrapper from './workspace-wrapper'; const BotBuilder = observer(() => { const { dashboard, app } = useDBotStore(); const { active_tab, has_started_onboarding_tour, has_started_bot_builder_tour, is_preview_on_popup } = dashboard; - const [is_tour_running] = React.useState(true); const { onMount, onUnmount } = app; const el_ref = React.useRef(null); @@ -49,22 +47,10 @@ const BotBuilder = observer(() => { {has_started_bot_builder_tour && active_tab === 1 && !has_started_onboarding_tour && ( <> - + - + )} diff --git a/packages/bot-web-ui/src/components/dashboard/dashboard.scss b/packages/bot-web-ui/src/components/dashboard/dashboard.scss index 4b8b13a5a7f0..560155aec485 100644 --- a/packages/bot-web-ui/src/components/dashboard/dashboard.scss +++ b/packages/bot-web-ui/src/components/dashboard/dashboard.scss @@ -454,13 +454,12 @@ } } - &__image { + &__image, + &__media { background: var(--general-section-1); text-align: center; padding: 0.8rem; width: 100%; - margin-bottom: 0.8rem; - height: 50%; } img { @@ -544,10 +543,6 @@ } } - &__content { - color: $color-white; - } - &__status { height: 5rem; display: flex; @@ -620,7 +615,6 @@ opacity: 0; color: var(--text-prominent); padding: 1rem 0; - font-size: 1.2rem; &--open { opacity: 1; diff --git a/packages/bot-web-ui/src/components/dashboard/dashboard.tsx b/packages/bot-web-ui/src/components/dashboard/dashboard.tsx index fcdb06d2dc41..50824e31994a 100644 --- a/packages/bot-web-ui/src/components/dashboard/dashboard.tsx +++ b/packages/bot-web-ui/src/components/dashboard/dashboard.tsx @@ -11,19 +11,13 @@ import { DBOT_TABS, TAB_IDS } from 'Constants/bot-contents'; import { useDBotStore } from 'Stores/useDBotStore'; import RunPanel from '../run-panel'; import RunStrategy from './dashboard-component/run-strategy'; +import TourEndDialog from './dbot-tours/common/tour-end-dialog'; +import TourStartDialog from './dbot-tours/common/tour-start-dialog'; +import DesktopTours from './dbot-tours/desktop-tours'; +import MobileTours from './dbot-tours/mobile-tours'; +import { getTourSettings, setTourSettings, setTourType, tour_status_ended, tour_type } from './dbot-tours/utils'; import DashboardComponent from './dashboard-component'; -import { - DBOT_ONBOARDING, - getTourSettings, - setTourSettings, - setTourType, - tour_status_ended, - tour_type, -} from './joyride-config'; -import ReactJoyrideWrapper from './react-joyride-wrapper'; import StrategyNotification from './strategy-notification'; -import TourSlider from './tour-slider'; -import TourTriggrerDialog from './tour-trigger-dialog'; import Tutorial from './tutorial-tab'; const Dashboard = observer(() => { @@ -39,6 +33,7 @@ const Dashboard = observer(() => { setHasTourEnded, has_started_bot_builder_tour, is_tour_dialog_visible, + has_tour_ended, setActiveTab, setBotBuilderTokenCheck, setOnBoardingTokenCheck, @@ -229,15 +224,13 @@ const Dashboard = observer(() => { 'dashboard__container--active': has_tour_started && active_tab === DASHBOARD && is_mobile, })} > - {(active_tab === DASHBOARD || active_tab === BOT_BUILDER) && } + {!has_tour_ended && (active_tab === DASHBOARD || active_tab === BOT_BUILDER) ? ( + + ) : ( + + )} - {has_tour_started && - active_tab === DASHBOARD && - (is_mobile ? ( - - ) : ( - - ))} + {has_tour_started && active_tab === DASHBOARD && (is_mobile ? : )} { + const { dashboard } = useDBotStore(); + const { active_tab, has_tour_ended, is_tour_dialog_visible, setTourDialogVisibility, toggleOnConfirm } = dashboard; + + const is_mobile = isMobile(); + + const toggleTour = (value: boolean, type: string) => { + if (tour_type.key === 'bot_builder') { + if (type === 'onConfirm') { + toggleOnConfirm(active_tab, value); + } else { + setTourSettings(new Date().getTime(), `${tour_type.key}_token`); + } + } + setTourDialogVisibility(false); + }; + + const getTourContent = () => { + return ( + <> +
+ +
+
+ run the bot to test out the strategy.'} + components={[]} + /> +
+
+ Tutorials tab.' + } + components={[]} + /> +
+ + ); + }; + + const onHandleConfirm = React.useCallback(() => { + toggleTour(tour_status_ended.key === 'finished', 'onConfirm'); + tour_status_ended.key = ''; + return tour_status_ended.key; + }, [has_tour_ended, active_tab]); + + return ( +
+ toggleTour(false, 'onCancel')} + confirm_button_text={localize('OK')} + onConfirm={onHandleConfirm} + is_mobile_full_width + className={classNames('dc-dialog tour-dialog', { + 'tour-dialog--end': has_tour_ended, + })} + has_close_icon={false} + > +
+ + {localize('Congratulations')} + +
+
+ + {getTourContent()} + +
+
+
+ ); +}); + +export default TourEndDialog; diff --git a/packages/bot-web-ui/src/components/dashboard/dbot-tours/common/tour-start-dialog.tsx b/packages/bot-web-ui/src/components/dashboard/dbot-tours/common/tour-start-dialog.tsx new file mode 100644 index 000000000000..4419063e43c5 --- /dev/null +++ b/packages/bot-web-ui/src/components/dashboard/dbot-tours/common/tour-start-dialog.tsx @@ -0,0 +1,101 @@ +import React from 'react'; +import classNames from 'classnames'; +import { Dialog, Text } from '@deriv/components'; +import { isMobile } from '@deriv/shared'; +import { observer } from '@deriv/stores'; +import { Localize, localize } from '@deriv/translations'; +import { DBOT_TABS } from 'Constants/bot-contents'; +import { useDBotStore } from '../../../../stores/useDBotStore'; +import { bot_builder_tour_header, onboarding_tour_header, tourDialogAction, tourDialogInfo } from '../config'; +import { setTourSettings, tour_type } from '../utils'; + +const TourStartDialog = observer(() => { + const { dashboard } = useDBotStore(); + const { + active_tab, + has_tour_ended, + is_tour_dialog_visible, + setTourDialogVisibility, + toggleOnConfirm, + setBotBuilderTourState, + setOnBoardTourRunState, + setTourActive, + } = dashboard; + + const is_mobile = isMobile(); + const current_tour_type_key = tour_type?.key; + const toggleTour = (value: boolean, type: string) => { + if (type === 'onConfirm') toggleOnConfirm(active_tab, value); + setTourSettings(new Date().getTime(), `${current_tour_type_key}_token`); + setTourDialogVisibility(false); + }; + + const onboard_tour = active_tab === DBOT_TABS.DASHBOARD; + + const getTourContent = () => { + return ( + <> + {onboard_tour ? ( + ]} + /> + ) : ( + <> +
{tourDialogInfo}
+
{tourDialogAction}
+
+ Tutorials tab.'} + components={[]} + /> +
+ + )} + + ); + }; + + const onHandleConfirm = () => { + setTourActive(true); + if (onboard_tour) setOnBoardTourRunState(true); + else setBotBuilderTourState(true); + setTourDialogVisibility(false); + }; + const header_text_size = is_mobile ? 'xs' : 's'; + const content_text_size = is_mobile ? 'xxs' : 'xs'; + + const tour_headers = active_tab === 0 ? onboarding_tour_header : bot_builder_tour_header; + + return ( +
+ toggleTour(false, 'onCancel')} + confirm_button_text={localize('Start')} + onConfirm={onHandleConfirm} + is_mobile_full_width + className={classNames('dc-dialog tour-dialog', { + 'tour-dialog--end': has_tour_ended, + })} + has_close_icon={false} + > +
+ + {tour_headers} + +
+
+ + {getTourContent()} + +
+
+
+ ); +}); + +export default TourStartDialog; diff --git a/packages/bot-web-ui/src/components/dashboard/dbot-tours/config/__tests__/config.spec.tsx b/packages/bot-web-ui/src/components/dashboard/dbot-tours/config/__tests__/config.spec.tsx new file mode 100644 index 000000000000..6a39f2683124 --- /dev/null +++ b/packages/bot-web-ui/src/components/dashboard/dbot-tours/config/__tests__/config.spec.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import { mockStore, StoreProvider } from '@deriv/stores'; +import { render, screen } from '@testing-library/react'; +import { mock_ws } from 'Utils/mock'; +import RootStore from 'Stores/root-store'; +import { DBotStoreProvider, mockDBotStore } from 'Stores/useDBotStore'; +import BotBuilderTour from '../../mobile-tours/bot-builder-tour'; +import OnboardingTour from '../../mobile-tours/onboarding-tour'; +import { BOT_BUILDER_MOBILE, DBOT_ONBOARDING_MOBILE } from '../index'; + +jest.mock('@deriv/bot-skeleton/src/scratch/blockly', () => jest.fn()); +jest.mock('@deriv/bot-skeleton/src/scratch/dbot', () => jest.fn()); +jest.mock('@deriv/bot-skeleton/src/scratch/hooks/block_svg', () => jest.fn()); +jest.mock('@deriv/deriv-charts', () => ({ + setSmartChartsPublicPath: jest.fn(), +})); +describe('Tour Config Data', () => { + let wrapper: ({ children }: { children: JSX.Element }) => JSX.Element, mock_DBot_store: RootStore | undefined; + + beforeEach(() => { + const mock_store = mockStore({}); + mock_DBot_store = mockDBotStore(mock_store, mock_ws); + + wrapper = ({ children }: { children: JSX.Element }) => ( + + + {children} + + + ); + }); + it('DBOT_ONBOARDING_MOBILE is called', () => { + render(, { + wrapper, + }); + const first_step = DBOT_ONBOARDING_MOBILE[0]; + expect(screen.getByText(first_step.header)).toBeInTheDocument(); + }); + + it('BOT_BUILDER_MOBILE is called', () => { + render(, { + wrapper, + }); + const first_step = BOT_BUILDER_MOBILE[0]; + expect(screen.getByText(first_step.header)).toBeInTheDocument(); + }); +}); diff --git a/packages/bot-web-ui/src/components/dashboard/joyride-config.tsx b/packages/bot-web-ui/src/components/dashboard/dbot-tours/config/index.tsx similarity index 86% rename from packages/bot-web-ui/src/components/dashboard/joyride-config.tsx rename to packages/bot-web-ui/src/components/dashboard/dbot-tours/config/index.tsx index 17b4a28a08a7..6efce48b7ef4 100644 --- a/packages/bot-web-ui/src/components/dashboard/joyride-config.tsx +++ b/packages/bot-web-ui/src/components/dashboard/dbot-tours/config/index.tsx @@ -1,76 +1,17 @@ -import { getImageLocation } from '../../public-path'; +import { getImageLocation } from '../../../../public-path'; import React from 'react'; -import { CallBackProps } from 'react-joyride'; import { Icon, Text } from '@deriv/components'; -import { getUrlBase } from '@deriv/shared'; +import { getUrlBase, isMobile } from '@deriv/shared'; import { Localize, localize } from '@deriv/translations'; -import { getSetting, storeSetting } from '../../utils/settings'; -import TourGuide from './tour-guide'; +import TourSteps from '../desktop-tours/common/tour-steps'; + +const is_mobile = isMobile(); type TJoyrideConfig = Record< 'showProgress' | 'spotlightClicks' | 'disableBeacon' | 'disableOverlay' | 'disableCloseOnEsc', boolean >; -type TTourStatus = { - key: string; - toggle: string; - type: string; -}; - -export type TTourType = Pick; - -export const setTourSettings = (param: number | { [key: string]: string }, type: string) => { - if (type === `${tour_type.key}_token`) { - return storeSetting(`${tour_type.key}_token`, param); - } - return storeSetting(`${tour_type.key}_status`, param); -}; - -export const getTourSettings = (type: string) => { - if (type === 'token') { - return getSetting(`${tour_type.key}_token`); - } - return getSetting(`${tour_type.key}_status`); -}; - -export const tour_type: TTourType = { - key: 'onboard_tour', -}; - -export const setTourType = (param: string) => { - tour_type.key = param; -}; - -export const tour_status_ended: TTourStatus = { - key: '', - toggle: '', - type: `${tour_type.key}_status`, -}; - -let tour: { [key: string]: string } = {}; -let current_target: number | undefined; - -export const handleJoyrideCallback = (data: CallBackProps) => { - const { action, index, status } = data; - if (status === 'finished') { - tour_status_ended.key = status; - } - if (action === 'close') { - tour_status_ended.toggle = action; - } - if (current_target !== index) { - tour = {}; - tour.status = status; - tour.action = action; - } - current_target = index; - setTourSettings(tour, 'tour'); - //added trigger to create new listner on local storage - window.dispatchEvent(new Event('storage')); - setTourSettings(new Date().getTime(), `${tour_type.key}_token`); -}; - const joyride_props: TJoyrideConfig = { showProgress: false, spotlightClicks: false, @@ -83,7 +24,7 @@ export const DBOT_ONBOARDING = [ { target: '#id-bot-builder', content: ( - @@ -470,20 +411,19 @@ export const BOT_BUILDER_TOUR = [ }, ]; -export type TStepMobile = { +export type TMobileTourConfig = { header: string; - content: React.ReactElement[]; - key: number; - step_key?: number; + content: Array; + tour_step_key: number; img?: string; media?: string; }; -export const BOT_BUILDER_MOBILE: TStepMobile[] = [ +export const BOT_BUILDER_MOBILE: TMobileTourConfig[] = [ { header: localize('Step 1'), content: [], - key: 1, + tour_step_key: 1, }, { header: localize('Step 2'), @@ -493,7 +433,7 @@ export const BOT_BUILDER_MOBILE: TStepMobile[] = [ i18n_default_text={`Next, import your bot directly from your mobile device or from Google Drive.`} />, ], - key: 2, + tour_step_key: 2, }, { header: localize('Step 3'), @@ -503,11 +443,11 @@ export const BOT_BUILDER_MOBILE: TStepMobile[] = [ i18n_default_text={`Once imported, you will see a preview of the bot on the workspace. Click run to start trading with this bot.`} />, ], - key: 3, + tour_step_key: 3, }, ]; -export const DBOT_ONBOARDING_MOBILE: TStepMobile[] = [ +export const DBOT_ONBOARDING_MOBILE: TMobileTourConfig[] = [ { header: localize('Get started on Deriv Bot'), content: [ @@ -517,8 +457,7 @@ export const DBOT_ONBOARDING_MOBILE: TStepMobile[] = [ components={[]} />, ], - key: 1, - step_key: 0, + tour_step_key: 1, }, { header: localize('Import or choose your bot'), @@ -529,8 +468,7 @@ export const DBOT_ONBOARDING_MOBILE: TStepMobile[] = [ />, ], media: getUrlBase('/public/videos/dbot-mobile-onboarding-step-1.mp4'), - key: 2, - step_key: 1, + tour_step_key: 2, }, { header: localize('Monitor the market'), @@ -538,8 +476,7 @@ export const DBOT_ONBOARDING_MOBILE: TStepMobile[] = [ , ], media: getUrlBase('/public/videos/dbot-mobile-onboarding-step-2.mp4'), - key: 3, - step_key: 2, + tour_step_key: 3, }, { header: localize('Learn more with our tutorials'), @@ -550,8 +487,7 @@ export const DBOT_ONBOARDING_MOBILE: TStepMobile[] = [ />, ], media: getUrlBase('/public/videos/dbot-mobile-onboarding-step-3.mp4'), - key: 4, - step_key: 3, + tour_step_key: 4, }, { header: localize('Use these shortcuts'), @@ -562,21 +498,15 @@ export const DBOT_ONBOARDING_MOBILE: TStepMobile[] = [ i18n_default_text='You can also import or build your bot using any of these shortcuts.' />, ], - key: 5, - step_key: 4, + tour_step_key: 5, }, { header: localize('Check your bot’s performance'), + media: getUrlBase('/public/videos/dbot-mobile-onboarding-step-5.mp4'), content: [ - ]} - />, + , ], - media: getUrlBase('/public/videos/dbot-mobile-onboarding-step-5.mp4'), - key: 6, - step_key: 5, + tour_step_key: 6, }, { header: localize('Run your bot'), @@ -588,14 +518,46 @@ export const DBOT_ONBOARDING_MOBILE: TStepMobile[] = [ components={[]} />, ], - key: 7, - step_key: 6, + tour_step_key: 7, }, { header: localize('Want to retake the tour?'), img: getImageLocation('dbot-mobile-onboarding-step-7.png'), content: [], - key: 8, - step_key: 7, + tour_step_key: 8, }, ]; + +export const tourDialogInfo = is_mobile ? ( + +) : ( + +); + +export const tourDialogAction = is_mobile ? ( + +) : ( + +); + +export const onboarding_tour_header = ( + +); + +export const bot_builder_tour_header = is_mobile ? ( + +) : ( + +); diff --git a/packages/bot-web-ui/src/components/dashboard/dbot-tours/desktop-tours/bot-builder-tour.tsx b/packages/bot-web-ui/src/components/dashboard/dbot-tours/desktop-tours/bot-builder-tour.tsx new file mode 100644 index 000000000000..5db1d6576f5a --- /dev/null +++ b/packages/bot-web-ui/src/components/dashboard/dbot-tours/desktop-tours/bot-builder-tour.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { observer } from '@deriv/stores'; +import { BOT_BUILDER_TOUR } from '../config'; +import ReactJoyrideWrapper from './common/react-joyride-wrapper'; + +const BotBuilderTour = observer(() => { + const [is_tour_running] = React.useState(true); + + return ( + + ); +}); + +export default BotBuilderTour; diff --git a/packages/bot-web-ui/src/components/dashboard/react-joyride-wrapper.tsx b/packages/bot-web-ui/src/components/dashboard/dbot-tours/desktop-tours/common/react-joyride-wrapper.tsx similarity index 96% rename from packages/bot-web-ui/src/components/dashboard/react-joyride-wrapper.tsx rename to packages/bot-web-ui/src/components/dashboard/dbot-tours/desktop-tours/common/react-joyride-wrapper.tsx index 3740e2dfd319..9b485b1c8187 100644 --- a/packages/bot-web-ui/src/components/dashboard/react-joyride-wrapper.tsx +++ b/packages/bot-web-ui/src/components/dashboard/dbot-tours/desktop-tours/common/react-joyride-wrapper.tsx @@ -1,7 +1,7 @@ import React from 'react'; import ReactJoyride, { Step, Styles } from 'react-joyride'; import { localize } from '@deriv/translations'; -import { handleJoyrideCallback } from './joyride-config'; +import { handleJoyrideCallback } from '../../utils'; const common_tour_button_properties = { fontWeight: '700', diff --git a/packages/bot-web-ui/src/components/dashboard/tour-guide.tsx b/packages/bot-web-ui/src/components/dashboard/dbot-tours/desktop-tours/common/tour-steps.tsx similarity index 91% rename from packages/bot-web-ui/src/components/dashboard/tour-guide.tsx rename to packages/bot-web-ui/src/components/dashboard/dbot-tours/desktop-tours/common/tour-steps.tsx index 73b353cdf503..603bd703a1ad 100644 --- a/packages/bot-web-ui/src/components/dashboard/tour-guide.tsx +++ b/packages/bot-web-ui/src/components/dashboard/dbot-tours/desktop-tours/common/tour-steps.tsx @@ -1,9 +1,10 @@ import React from 'react'; +import { getUUID } from '@deriv/bot-skeleton/src/services/tradeEngine/utils/helpers'; import { Icon, Text } from '@deriv/components'; import { observer } from '@deriv/stores'; import { useDBotStore } from 'Stores/useDBotStore'; -type TTourGuide = { +type TTourSteps = { content: string[] | React.ReactElement[]; media?: string; label: string | boolean; @@ -12,8 +13,8 @@ type TTourGuide = { has_localize_component?: boolean; }; -const TourGuide = observer( - ({ content, media, label, step_index, has_localize_component = false, show_actions = true }: TTourGuide) => { +const TourSteps = observer( + ({ content, media, label, step_index, has_localize_component = false, show_actions = true }: TTourSteps) => { const { dashboard } = useDBotStore(); const { onCloseTour } = dashboard; @@ -57,7 +58,7 @@ const TourGuide = observer( return has_localize_component ? ( content_data ) : ( -
+
{content_data} @@ -73,4 +74,4 @@ const TourGuide = observer( } ); -export default TourGuide; +export default TourSteps; diff --git a/packages/bot-web-ui/src/components/dashboard/dbot-tours/desktop-tours/desktop-tours.tsx b/packages/bot-web-ui/src/components/dashboard/dbot-tours/desktop-tours/desktop-tours.tsx new file mode 100644 index 000000000000..3f4e723ab39c --- /dev/null +++ b/packages/bot-web-ui/src/components/dashboard/dbot-tours/desktop-tours/desktop-tours.tsx @@ -0,0 +1,13 @@ +import React from 'react'; +import { observer } from '@deriv/stores'; +import { useDBotStore } from 'Stores/useDBotStore'; +import BotBuilderTour from './bot-builder-tour'; +import OnboardingTour from './onboarding-tour'; + +const DesktopTours = observer(() => { + const { dashboard } = useDBotStore(); + const { has_started_onboarding_tour } = dashboard; + return <>{has_started_onboarding_tour ? : }; +}); + +export default DesktopTours; diff --git a/packages/bot-web-ui/src/components/dashboard/dbot-tours/desktop-tours/index.ts b/packages/bot-web-ui/src/components/dashboard/dbot-tours/desktop-tours/index.ts new file mode 100644 index 000000000000..c22f725f65af --- /dev/null +++ b/packages/bot-web-ui/src/components/dashboard/dbot-tours/desktop-tours/index.ts @@ -0,0 +1,3 @@ +import DesktopTours from './desktop-tours'; + +export default DesktopTours; diff --git a/packages/bot-web-ui/src/components/dashboard/dbot-tours/desktop-tours/onboarding-tour.tsx b/packages/bot-web-ui/src/components/dashboard/dbot-tours/desktop-tours/onboarding-tour.tsx new file mode 100644 index 000000000000..eb35d0abd202 --- /dev/null +++ b/packages/bot-web-ui/src/components/dashboard/dbot-tours/desktop-tours/onboarding-tour.tsx @@ -0,0 +1,10 @@ +import React from 'react'; +import { observer } from '@deriv/stores'; +import { DBOT_ONBOARDING } from '../config'; +import ReactJoyrideWrapper from './common/react-joyride-wrapper'; + +const OnboardingTour = observer(() => { + return ; +}); + +export default OnboardingTour; diff --git a/packages/bot-web-ui/src/components/dashboard/dbot-tours/mobile-tours/__tests__/bot-builder-tour.spec.tsx b/packages/bot-web-ui/src/components/dashboard/dbot-tours/mobile-tours/__tests__/bot-builder-tour.spec.tsx new file mode 100644 index 000000000000..fb61ae54ccf8 --- /dev/null +++ b/packages/bot-web-ui/src/components/dashboard/dbot-tours/mobile-tours/__tests__/bot-builder-tour.spec.tsx @@ -0,0 +1,83 @@ +// eslint-disable-next-line simple-import-sort/imports +import '@testing-library/react/dont-cleanup-after-each'; +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/root-store'; +import { DBotStoreProvider, mockDBotStore } from 'Stores/useDBotStore'; +import BotBuilderTour from '../bot-builder-tour'; + +jest.mock('@deriv/bot-skeleton/src/scratch/blockly', () => jest.fn()); +jest.mock('@deriv/bot-skeleton/src/scratch/dbot', () => jest.fn()); +jest.mock('@deriv/bot-skeleton/src/scratch/hooks/block_svg', () => jest.fn()); +jest.mock('@deriv/deriv-charts', () => ({ + setSmartChartsPublicPath: jest.fn(), +})); + +describe('', () => { + let wrapper: ({ children }: { children: JSX.Element }) => JSX.Element, mock_DBot_store: RootStore | undefined; + + beforeAll(() => { + const mock_store = mockStore({}); + mock_DBot_store = mockDBotStore(mock_store, mock_ws); + + wrapper = ({ children }: { children: JSX.Element }) => ( + + + {children} + + + ); + }); + + it('renders botbuilder tour', () => { + const { container } = render(, { + wrapper, + }); + expect(container).toBeInTheDocument(); + }); + + it('shows the tour content when tour starts', () => { + const tourElement = screen.queryByTestId('botbuilder-tour-mobile'); + expect(tourElement).toBeInTheDocument(); + }); + + it('should show prev button if next button is clicked', async () => { + const nextButton = screen.getByTestId('next-bot-builder-tour'); + userEvent.click(nextButton); + await waitFor(() => { + const prevButton = screen.getByTestId('prev-bot-builder-tour'); + expect(prevButton).toBeInTheDocument(); + }); + }); + + it('should not show prev button if we reach to step 1', async () => { + const prevButton = screen.getByTestId('prev-bot-builder-tour'); + userEvent.click(prevButton); + await waitFor(() => { + expect(prevButton).not.toBeInTheDocument(); + }); + }); + + it('should show step 2 on next button click', () => { + const nextButton = screen.getByTestId('next-bot-builder-tour'); + userEvent.click(nextButton); + const navBar = screen.getByTestId('dbot-acc-id'); + expect(navBar).toHaveTextContent('Step 2'); + }); + + it('should show step 3 on next button click', () => { + const nextButton = screen.getByTestId('next-bot-builder-tour'); + userEvent.click(nextButton); + const navBar = screen.getByTestId('dbot-acc-id'); + expect(navBar).toHaveTextContent('Step 3'); + }); + + it('calls onCloseTour when Finish is clicked', () => { + const endTourButton = screen.getByTestId('finish-bot-builder-tour'); + userEvent.click(endTourButton); + expect(mock_DBot_store?.dashboard.has_started_bot_builder_tour).toBe(false); + }); +}); diff --git a/packages/bot-web-ui/src/components/dashboard/dbot-tours/mobile-tours/__tests__/mobile-tours.spec.tsx b/packages/bot-web-ui/src/components/dashboard/dbot-tours/mobile-tours/__tests__/mobile-tours.spec.tsx new file mode 100644 index 000000000000..593c03848b26 --- /dev/null +++ b/packages/bot-web-ui/src/components/dashboard/dbot-tours/mobile-tours/__tests__/mobile-tours.spec.tsx @@ -0,0 +1,45 @@ +import React from 'react'; +import { mockStore, StoreProvider } from '@deriv/stores'; +import { render, screen } from '@testing-library/react'; +import { mock_ws } from 'Utils/mock'; +import RootStore from 'Stores/root-store'; +import { DBotStoreProvider, mockDBotStore } from 'Stores/useDBotStore'; +import MobileTours from '../mobile-tours'; + +jest.mock('@deriv/bot-skeleton/src/scratch/blockly', () => jest.fn()); +jest.mock('@deriv/bot-skeleton/src/scratch/dbot', () => jest.fn()); +jest.mock('@deriv/bot-skeleton/src/scratch/hooks/block_svg', () => jest.fn()); +jest.mock('@deriv/deriv-charts', () => ({ + setSmartChartsPublicPath: jest.fn(), +})); +describe('', () => { + let wrapper: ({ children }: { children: JSX.Element }) => JSX.Element, mock_DBot_store: RootStore | undefined; + + beforeAll(() => { + const mock_store = mockStore({}); + mock_DBot_store = mockDBotStore(mock_store, mock_ws); + + wrapper = ({ children }: { children: JSX.Element }) => ( + + + {children} + + + ); + }); + it('should render OnboardingTour tour', () => { + mock_DBot_store?.dashboard.setOnBoardTourRunState(true); + render(, { + wrapper, + }); + expect(screen.getByTestId('onboarding-tour-mobile')).toBeInTheDocument(); + }); + + it('should render BotBuilderTour tour', () => { + mock_DBot_store?.dashboard.setOnBoardTourRunState(false); + render(, { + wrapper, + }); + expect(screen.getByTestId('botbuilder-tour-mobile')).toBeInTheDocument(); + }); +}); diff --git a/packages/bot-web-ui/src/components/dashboard/dbot-tours/mobile-tours/__tests__/onboarding-tour.spec.tsx b/packages/bot-web-ui/src/components/dashboard/dbot-tours/mobile-tours/__tests__/onboarding-tour.spec.tsx new file mode 100644 index 000000000000..cee3fced206f --- /dev/null +++ b/packages/bot-web-ui/src/components/dashboard/dbot-tours/mobile-tours/__tests__/onboarding-tour.spec.tsx @@ -0,0 +1,119 @@ +// eslint-disable-next-line simple-import-sort/imports +import '@testing-library/react/dont-cleanup-after-each'; +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/root-store'; +import { DBotStoreProvider, mockDBotStore } from 'Stores/useDBotStore'; +import OnboardingTour from '../onboarding-tour'; + +jest.mock('@deriv/bot-skeleton/src/scratch/blockly', () => jest.fn()); +jest.mock('@deriv/bot-skeleton/src/scratch/dbot', () => jest.fn()); +jest.mock('@deriv/bot-skeleton/src/scratch/hooks/block_svg', () => jest.fn()); +jest.mock('@deriv/deriv-charts', () => ({ + setSmartChartsPublicPath: jest.fn(), +})); + +describe('', () => { + let wrapper: ({ children }: { children: JSX.Element }) => JSX.Element, mock_DBot_store: RootStore | undefined; + + beforeAll(() => { + const mock_store = mockStore({}); + mock_DBot_store = mockDBotStore(mock_store, mock_ws); + + wrapper = ({ children }: { children: JSX.Element }) => ( + + + {children} + + + ); + }); + + it('renders onboarding tour', () => { + const { container } = render(, { + wrapper, + }); + expect(container).toBeInTheDocument(); + }); + + it('shows the tour content when tour starts', () => { + const tourElement = screen.getByTestId('onboarding-tour-mobile'); + expect(tourElement).toHaveClass('dbot-slider--active'); + }); + + it('calls onCloseTour when Skip is clicked', () => { + const skipTourButton = screen.getByTestId('skip-onboard-tour'); + userEvent.click(skipTourButton); + expect(mock_DBot_store?.dashboard.has_started_onboarding_tour).toBe(false); + }); + + it('should show prev button if next button is clicked', async () => { + const nextButton = screen.getByTestId('next-onboard-tour'); + userEvent.click(nextButton); + userEvent.click(nextButton); + await waitFor(() => { + const prevButton = screen.getByTestId('prev-onboard-tour'); + expect(prevButton).toBeInTheDocument(); + }); + }); + + it('should not show prev button if we reach to step 1', async () => { + const prevButton = screen.getByTestId('prev-onboard-tour'); + userEvent.click(prevButton); + await waitFor(() => { + expect(prevButton).not.toBeInTheDocument(); + }); + }); + + it('calls onCloseTour when Exit Tour is clicked', () => { + const nextButton = screen.getByTestId('next-onboard-tour'); + userEvent.click(nextButton); + const exitTourButton = screen.getByTestId('exit-onboard-tour'); + userEvent.click(exitTourButton); + expect(mock_DBot_store?.dashboard.has_started_onboarding_tour).toBe(false); + }); + + it('should render step 3 on next button click', () => { + const nextButton = screen.getByTestId('next-onboard-tour'); + userEvent.click(nextButton); + const navBar = screen.getByTestId('dbot-onboard-slider__navbar'); + expect(navBar).toHaveTextContent('3/7'); + }); + + it('should render step 4 on next button click', () => { + const nextButton = screen.getByTestId('next-onboard-tour'); + userEvent.click(nextButton); + const navBar = screen.getByTestId('dbot-onboard-slider__navbar'); + expect(navBar).toHaveTextContent('4/7'); + }); + + it('should render step 5 on next button click', () => { + const nextButton = screen.getByTestId('next-onboard-tour'); + userEvent.click(nextButton); + const navBar = screen.getByTestId('dbot-onboard-slider__navbar'); + expect(navBar).toHaveTextContent('5/7'); + }); + + it('should render step 6 on next button click', () => { + const nextButton = screen.getByTestId('next-onboard-tour'); + userEvent.click(nextButton); + const navBar = screen.getByTestId('dbot-onboard-slider__navbar'); + expect(navBar).toHaveTextContent('6/7'); + }); + + it('should render step 7 on next button click', () => { + const nextButton = screen.getByTestId('next-onboard-tour'); + userEvent.click(nextButton); + const navBar = screen.getByTestId('dbot-onboard-slider__navbar'); + expect(navBar).toHaveTextContent('7/7'); + }); + + it('calls onCloseTour when Finish is clicked', () => { + const endTourButton = screen.getByTestId('finish-onboard-tour'); + userEvent.click(endTourButton); + expect(mock_DBot_store?.dashboard.has_started_onboarding_tour).toBe(false); + }); +}); diff --git a/packages/bot-web-ui/src/components/dashboard/dbot-tours/mobile-tours/bot-builder-tour.tsx b/packages/bot-web-ui/src/components/dashboard/dbot-tours/mobile-tours/bot-builder-tour.tsx new file mode 100644 index 000000000000..5496f994b291 --- /dev/null +++ b/packages/bot-web-ui/src/components/dashboard/dbot-tours/mobile-tours/bot-builder-tour.tsx @@ -0,0 +1,68 @@ +import React from 'react'; +import { ProgressBarTracker } from '@deriv/components'; +import { observer } from '@deriv/stores'; +import { localize } from '@deriv/translations'; +import { useDBotStore } from 'Stores/useDBotStore'; +import { BOT_BUILDER_MOBILE } from '../config'; +import { highlightLoadModalButton } from '../utils'; +import Accordion from './common/accordion'; +import TourButton from './common/tour-button'; + +const BotBuilderTour = observer(() => { + const { dashboard, load_modal } = useDBotStore(); + const { toggleTourLoadModal } = load_modal; + const { onTourEnd, has_started_bot_builder_tour, setTourActiveStep } = dashboard; + const [tour_step, setTourStep] = React.useState(1); + const content_data = BOT_BUILDER_MOBILE.find(({ tour_step_key }) => { + return tour_step_key === tour_step; + }); + const test_id = tour_step === 3 ? 'finish-bot-builder-tour' : 'next-bot-builder-tour'; + + React.useEffect(() => { + setTourActiveStep(tour_step); + //component does not rerender so calling this to highlight + highlightLoadModalButton(has_started_bot_builder_tour, tour_step); + if (tour_step === 2) toggleTourLoadModal(true); + else toggleTourLoadModal(false); + }, [tour_step]); + + const tour_button_text = tour_step === 3 ? localize('Finish') : localize('Next'); + return ( +
+ {content_data && } +
+
+ { + v.tour_step_key.toString())} + setStep={setTourStep} + /> + } +
+
+ {tour_step !== 1 && ( + { + setTourStep(tour_step - 1); + }} + label={localize('Previous')} + data-testid='prev-bot-builder-tour' + /> + )} + { + setTourStep(tour_step + 1); + onTourEnd(tour_step, false); + }} + label={tour_button_text} + data-testid={test_id} + /> +
+
+
+ ); +}); + +export default BotBuilderTour; diff --git a/packages/bot-web-ui/src/components/dashboard/dbot-tours/mobile-tours/common/__tests__/accordion.spec.tsx b/packages/bot-web-ui/src/components/dashboard/dbot-tours/mobile-tours/common/__tests__/accordion.spec.tsx new file mode 100644 index 000000000000..2577b3b9fb2d --- /dev/null +++ b/packages/bot-web-ui/src/components/dashboard/dbot-tours/mobile-tours/common/__tests__/accordion.spec.tsx @@ -0,0 +1,57 @@ +import React from 'react'; +import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import Accordion from '../accordion'; + +const mocked_props = { + content_data: [ + 'test dasdasdasde 1', + { + content: 'content 1', + header: 'header 1', + }, + { + content: 'content 2', + header: 'header 2', + }, + + { + content: 'content 3', + header: 'header 3', + }, + ], + expanded: true, + test_id: 'test_string', +}; + +describe('', () => { + it('should render Accordion with correct props and content', () => { + render(); + const accordion = screen.getByTestId('test_string'); + expect(accordion).toBeInTheDocument(); + }); + + it('should not render Accordion if content_data is empty', () => { + const props_with_empty_content = { + ...mocked_props, + content_data: null, + }; + render(); + const accordion = screen.queryByTestId('test_string'); + expect(accordion).not.toBeInTheDocument(); + }); + + it('should open accordion', async () => { + render(); + await waitFor(() => { + expect(screen.getByTestId('accordion-content')).toHaveClass('dbot-accordion__content--open'); + }); + }); + it('should close accordion', async () => { + render(); + const accordion = screen.getByTestId('test_string'); + fireEvent.click(accordion); + await waitFor(() => { + expect(screen.getByTestId('accordion-content')).not.toHaveClass('dbot-accordion__content--open'); + }); + }); +}); diff --git a/packages/bot-web-ui/src/components/dashboard/dbot-tours/mobile-tours/common/__tests__/tour-button.spec.tsx b/packages/bot-web-ui/src/components/dashboard/dbot-tours/mobile-tours/common/__tests__/tour-button.spec.tsx new file mode 100644 index 000000000000..9cd8e41ccd4b --- /dev/null +++ b/packages/bot-web-ui/src/components/dashboard/dbot-tours/mobile-tours/common/__tests__/tour-button.spec.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import { fireEvent, render, screen } from '@testing-library/react'; +import TourButton from '../tour-button'; + +const mocked_props = { + label: 'Start Tour', + type: 'primary', + onClick: jest.fn(), +}; +describe('', () => { + it('should render TourButton with label', () => { + render(); + const button = screen.getByRole('button', { name: /Start Tour/i }); + expect(button).toBeInTheDocument(); + }); + + it('should not render TourButton with label', () => { + const mocked_null_props = { + ...mocked_props, + label: null, + }; + render(); + const button = screen.queryByRole('button', { name: /Start Tour/i }); + expect(button).not.toBeInTheDocument(); + }); + + it('should render TourButton with specified type', () => { + render(); + const button = screen.getByRole('button', { name: /Start Tour/i }); + expect(button).toHaveClass('primary'); + }); + + it('should call onClick when the button is clicked', () => { + render(); + const button = screen.getByRole('button', { name: /Start Tour/i }); + fireEvent.click(button); + expect(mocked_props.onClick).toHaveBeenCalledTimes(1); + }); +}); diff --git a/packages/bot-web-ui/src/components/dashboard/dbot-tours/mobile-tours/common/accordion.tsx b/packages/bot-web-ui/src/components/dashboard/dbot-tours/mobile-tours/common/accordion.tsx new file mode 100644 index 000000000000..1f0866cb37a6 --- /dev/null +++ b/packages/bot-web-ui/src/components/dashboard/dbot-tours/mobile-tours/common/accordion.tsx @@ -0,0 +1,45 @@ +import React from 'react'; +import classNames from 'classnames'; +import { Icon, Text } from '@deriv/components'; +import { localize } from '@deriv/translations'; +import { TStepMobile } from '../../config'; + +type TAccordion = { + content_data: TStepMobile | null; + expanded: boolean; + test_id?: string; +}; + +const Accordion = ({ content_data, expanded = false, test_id = 'dbot-acc-id', ...props }: TAccordion) => { + const [is_open, setOpen] = React.useState(expanded); + if (!content_data) return null; + const { content, header } = content_data; + return ( +
+
+
setOpen(!is_open)}> +
+ + {localize(header)} + +
+
+ +
+
+
+ + {content} + +
+
+
+ ); +}; + +export default Accordion; diff --git a/packages/bot-web-ui/src/components/dashboard/dbot-tours/mobile-tours/common/tour-button.tsx b/packages/bot-web-ui/src/components/dashboard/dbot-tours/mobile-tours/common/tour-button.tsx new file mode 100644 index 000000000000..c1d92380adef --- /dev/null +++ b/packages/bot-web-ui/src/components/dashboard/dbot-tours/mobile-tours/common/tour-button.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { Text } from '@deriv/components'; + +type TTourButton = { + type?: string; + onClick: () => void; + label: string; +}; + +const TourButton = ({ label, type = 'default', ...props }: TTourButton) => { + if (!label) return null; + return ( + + ); +}; + +export default TourButton; diff --git a/packages/bot-web-ui/src/components/dashboard/dbot-tours/mobile-tours/index.ts b/packages/bot-web-ui/src/components/dashboard/dbot-tours/mobile-tours/index.ts new file mode 100644 index 000000000000..3b2bbf0f6e0b --- /dev/null +++ b/packages/bot-web-ui/src/components/dashboard/dbot-tours/mobile-tours/index.ts @@ -0,0 +1,3 @@ +import MobileTours from './mobile-tours'; + +export default MobileTours; diff --git a/packages/bot-web-ui/src/components/dashboard/dbot-tours/mobile-tours/mobile-tours.tsx b/packages/bot-web-ui/src/components/dashboard/dbot-tours/mobile-tours/mobile-tours.tsx new file mode 100644 index 000000000000..8e418111a0aa --- /dev/null +++ b/packages/bot-web-ui/src/components/dashboard/dbot-tours/mobile-tours/mobile-tours.tsx @@ -0,0 +1,13 @@ +import React from 'react'; +import { observer } from '@deriv/stores'; +import { useDBotStore } from 'Stores/useDBotStore'; +import BotBuilderTour from './bot-builder-tour'; +import OnboardingTour from './onboarding-tour'; + +const MobileTours = observer(() => { + const { dashboard } = useDBotStore(); + const { has_started_onboarding_tour } = dashboard; + return <>{has_started_onboarding_tour ? : }; +}); + +export default MobileTours; diff --git a/packages/bot-web-ui/src/components/dashboard/dbot-tours/mobile-tours/onboarding-tour.tsx b/packages/bot-web-ui/src/components/dashboard/dbot-tours/mobile-tours/onboarding-tour.tsx new file mode 100644 index 000000000000..4547334acc03 --- /dev/null +++ b/packages/bot-web-ui/src/components/dashboard/dbot-tours/mobile-tours/onboarding-tour.tsx @@ -0,0 +1,162 @@ +import React from 'react'; +import classNames from 'classnames'; +import { Icon, ProgressBarTracker, Text } from '@deriv/components'; +import { observer } from '@deriv/stores'; +import { localize } from '@deriv/translations'; +import { useDBotStore } from 'Stores/useDBotStore'; +import { DBOT_ONBOARDING_MOBILE, TMobileTourConfig } from '../config'; +import TourButton from './common/tour-button'; + +const default_tour_data = { + content: [], + header: '', + img: '', + tour_step_key: 1, +}; + +type TTourData = TMobileTourConfig & { + img: string; + tour_step_key: number; +}; + +const OnboardingTour = observer(() => { + const { dashboard } = useDBotStore(); + const { onCloseTour, onTourEnd, setTourActiveStep } = dashboard; + const [tour_step, setStep] = React.useState(1); + const [tour_data, setTourData] = React.useState(default_tour_data); + const { content, header, img, media, tour_step_key } = tour_data; + const start_button = tour_step === 1 ? localize('Start') : localize('Next'); + const tour_button_text = tour_step === 8 ? localize('Got it, thanks!') : start_button; + const test_id = tour_step_key === 8 ? 'finish-onboard-tour' : 'next-onboard-tour'; + const hide_prev_button = [1, 2, 8]; + + React.useEffect(() => { + DBOT_ONBOARDING_MOBILE.forEach(data => { + if (data.tour_step_key === tour_step) { + setTourData(data); + } + setTourActiveStep(tour_step); + }); + }, [tour_step]); + return ( +
+ {tour_step_key !== 1 && ( +
+ {`${tour_step_key - 1}/7`} + + + +
+ )} + {header && ( + + {localize(header)} + + )} + {media && ( +
+
+ )} + {img && ( +
+ +
+ )} + + {content && ( + <> + {content.map(data => { + return ( + + {data} + + ); + })} + + )} +
+
+ v.tour_step_key.toString())} + setStep={setStep} + /> +
+
+ {tour_step === 1 && ( + { + onCloseTour(); + }} + label={localize('Skip')} + data-testid='skip-onboard-tour' + /> + )} + {!hide_prev_button.includes(tour_step) && ( + { + setStep(tour_step - 1); + }} + label={localize('Previous')} + data-testid='prev-onboard-tour' + /> + )} + { + setStep(tour_step + 1); + onTourEnd(tour_step, true); + }} + label={tour_button_text} + data-testid={test_id} + /> +
+
+
+ ); +}); + +export default OnboardingTour; diff --git a/packages/bot-web-ui/src/components/dashboard/dbot-tours/utils/index.ts b/packages/bot-web-ui/src/components/dashboard/dbot-tours/utils/index.ts new file mode 100644 index 000000000000..f6509623f26e --- /dev/null +++ b/packages/bot-web-ui/src/components/dashboard/dbot-tours/utils/index.ts @@ -0,0 +1,70 @@ +import { CallBackProps } from 'react-joyride'; +import { getSetting, storeSetting } from 'Utils/settings'; + +type TTourStatus = { + key: string; + toggle: string; + type: string; +}; + +export const tour_type: TTourType = { + key: 'onboard_tour', +}; + +export const tour_status_ended: TTourStatus = { + key: '', + toggle: '', + type: `${tour_type.key}_status`, +}; + +let tour: Record = {}; +let current_target: number | undefined; + +export const handleJoyrideCallback = (data: CallBackProps) => { + const { action, index, status } = data; + if (status === 'finished') { + tour_status_ended.key = status; + } + if (action === 'close') { + tour_status_ended.toggle = action; + } + if (current_target !== index) { + tour = {}; + tour.status = status; + tour.action = action; + } + current_target = index; + setTourSettings(tour, 'tour'); + //added trigger to create new listner on local storage + window.dispatchEvent(new Event('storage')); + setTourSettings(new Date().getTime(), `${tour_type.key}_token`); +}; + +export type TTourType = Pick; + +export const setTourType = (param: string) => { + tour_type.key = param; +}; + +export const highlightLoadModalButton = (tour_active: boolean, step: number) => { + const el_ref = document.querySelector('.toolbar__group-btn svg:nth-child(2)'); + if (tour_active && step === 1) { + el_ref?.classList.add('dbot-tour-blink'); + } else { + el_ref?.classList.remove('dbot-tour-blink'); + } +}; + +export const setTourSettings = (param: number | { [key: string]: string }, type: string) => { + if (type === `${tour_type.key}_token`) { + return storeSetting(`${tour_type.key}_token`, param); + } + return storeSetting(`${tour_type.key}_status`, param); +}; + +export const getTourSettings = (type: string) => { + if (type === 'token') { + return getSetting(`${tour_type.key}_token`); + } + return getSetting(`${tour_type.key}_status`); +}; diff --git a/packages/bot-web-ui/src/components/dashboard/tour-slider.tsx b/packages/bot-web-ui/src/components/dashboard/tour-slider.tsx deleted file mode 100644 index abc7eb722880..000000000000 --- a/packages/bot-web-ui/src/components/dashboard/tour-slider.tsx +++ /dev/null @@ -1,233 +0,0 @@ -import React from 'react'; -import classNames from 'classnames'; -import { Icon, ProgressBarTracker, Text } from '@deriv/components'; -import { observer } from '@deriv/stores'; -import { localize } from '@deriv/translations'; -import { useDBotStore } from 'Stores/useDBotStore'; -import { BOT_BUILDER_MOBILE, DBOT_ONBOARDING_MOBILE, TStepMobile } from './joyride-config'; - -type TTourButton = { - type?: string; - onClick: () => void; - label: string; -}; - -type TAccordion = { - content_data: TStepMobile; - expanded: boolean; -}; - -const TourButton = ({ label, type = 'default', ...props }: TTourButton) => { - return ( - - ); -}; - -const Accordion = ({ content_data, expanded = false, ...props }: TAccordion) => { - const [is_open, setOpen] = React.useState(expanded); - const { content, header } = content_data; - - return ( -
-
-
setOpen(!is_open)}> -
- - {header} - -
-
- -
-
-
- - {content} - -
-
-
- ); -}; - -const TourSlider = observer(() => { - const { dashboard, load_modal } = useDBotStore(); - const { has_started_bot_builder_tour, has_started_onboarding_tour, onCloseTour, onTourEnd, setTourActiveStep } = - dashboard; - const { toggleTourLoadModal } = load_modal; - const [step, setStep] = React.useState(1); - const [slider_content, setContent] = React.useState([]); - const [slider_header, setHeader] = React.useState(''); - const [slider_image, setImg] = React.useState(''); - const [slider_media, setMedia] = React.useState(''); - const [step_key, setStepKey] = React.useState(0); - - React.useEffect(() => { - setTourActiveStep(step); - Object.values(!has_started_onboarding_tour ? BOT_BUILDER_MOBILE : DBOT_ONBOARDING_MOBILE).forEach(data => { - if (data.key === step) { - setContent(data?.content); - setHeader(data?.header); - setImg(data?.img || ''); - setMedia(data?.media || ''); - setStepKey(data?.step_key || 0); - } - }); - const el_ref = document.querySelector('.toolbar__group-btn svg:nth-child(2)'); - if (has_started_bot_builder_tour && step === 1) { - //component does not rerender - el_ref?.classList.add('dbot-tour-blink'); - } else { - el_ref?.classList.remove('dbot-tour-blink'); - } - if (has_started_bot_builder_tour && step === 2) { - toggleTourLoadModal(true); - } else toggleTourLoadModal(false); - }, [step]); - - const onChange = React.useCallback( - (param: string) => { - const MOBILE_TOUR = !has_started_onboarding_tour ? BOT_BUILDER_MOBILE : DBOT_ONBOARDING_MOBILE; - if (param === 'inc' && step < Object.keys(MOBILE_TOUR).length) setStep(step + 1); - else if (param === 'dec' && step > 1) setStep(step - 1); - else if (param === 'skip') onCloseTour(); - }, - [step] - ); - - const content_data = BOT_BUILDER_MOBILE.find(({ key }) => key === step); - const onClickNext = React.useCallback(() => { - onChange('inc'); - onTourEnd(step, has_started_onboarding_tour); - }, [step]); - - const bot_tour_text = !has_started_onboarding_tour && step === 3 ? localize('Finish') : localize('Next'); - - const tour_button_text = has_started_onboarding_tour && step_key === 0 ? localize('Start') : bot_tour_text; - - const onboarding_completed_text = - has_started_onboarding_tour && step === 8 ? localize('Got it, thanks!') : tour_button_text; - - return ( - <> -
- {has_started_onboarding_tour && slider_header && step_key !== 0 && ( -
- {`${step_key}/7`} - - - -
- )} - - {has_started_onboarding_tour && slider_header && ( - - {localize(slider_header)} - - )} - {has_started_onboarding_tour && - // eslint-disable-next-line no-nested-ternary - (slider_media ? ( -
-
- ) : slider_image ? ( -
- -
- ) : null)} - {has_started_onboarding_tour && slider_content && ( - <> - {slider_content?.map((data, index) => { - return ( - - {data} - - ); - })} - - )} - {!has_started_onboarding_tour && content_data && } -
-
- {(!has_started_onboarding_tour || (has_started_onboarding_tour && step !== 1)) && ( - - )} -
-
- {has_started_onboarding_tour && step === 1 && ( - { - onChange('skip'); - }} - label={localize('Skip')} - /> - )} - {((has_started_bot_builder_tour && step !== 1) || - (has_started_onboarding_tour && step !== 1 && step !== 2 && step !== 8)) && ( - { - onChange('dec'); - }} - label={localize('Previous')} - /> - )} - -
-
-
- - ); -}); - -export default TourSlider; diff --git a/packages/bot-web-ui/src/components/dashboard/tour-trigger-dialog.spec.tsx b/packages/bot-web-ui/src/components/dashboard/tour-trigger-dialog.spec.tsx deleted file mode 100644 index d9bbd0d994ec..000000000000 --- a/packages/bot-web-ui/src/components/dashboard/tour-trigger-dialog.spec.tsx +++ /dev/null @@ -1,180 +0,0 @@ -import React from 'react'; -import { mockStore, StoreProvider } from '@deriv/stores'; -// eslint-disable-next-line import/no-extraneous-dependencies -import { act, fireEvent, render, screen } from '@testing-library/react'; -import RootStore from '../../stores/root-store'; -import { DBotStoreProvider, mockDBotStore } from '../../stores/useDBotStore'; -import { setTourType } from './joyride-config'; -import TourTriggrerDialog from './tour-trigger-dialog'; - -jest.mock('@deriv/shared', () => ({ - ...jest.requireActual('@deriv/shared'), - isMobile: jest.fn(() => false), -})); - -jest.mock('@deriv/bot-skeleton/src/scratch/blockly', () => jest.fn()); -jest.mock('@deriv/bot-skeleton/src/scratch/dbot', () => ({ - saveRecentWorkspace: jest.fn(), - unHighlightAllBlocks: jest.fn(), -})); -jest.mock('@deriv/bot-skeleton/src/scratch/hooks/block_svg', () => ({ - blocksCoordinate: jest.fn(), -})); - -const mock_ws = { - authorized: { - subscribeProposalOpenContract: jest.fn(), - send: jest.fn(), - }, - storage: { - send: jest.fn(), - }, - contractUpdate: jest.fn(), - subscribeTicksHistory: jest.fn(), - forgetStream: jest.fn(), - activeSymbols: jest.fn(), - send: jest.fn(), -}; - -describe('', () => { - let wrapper: ({ children }: { children: JSX.Element }) => JSX.Element, mock_DBot_store: RootStore | undefined; - - beforeAll(() => { - const mock_store = mockStore({}); - mock_DBot_store = mockDBotStore(mock_store, mock_ws); - - wrapper = ({ children }: { children: JSX.Element }) => ( - - - {children} - - - ); - }); - - it('renders tour trigger component', () => { - const { container } = render(, { - wrapper, - }); - expect(container).toBeInTheDocument(); - }); - - it('should open tour trigger dialog', () => { - act(() => { - mock_DBot_store?.dashboard?.setTourDialogVisibility(true); - }); - render(, { - wrapper, - }); - expect(screen.getByText(/Get started on Deriv Bot/i)).toBeInTheDocument(); - }); - - it('should show tour end message', () => { - act(() => { - mock_DBot_store?.dashboard?.setTourDialogVisibility(true); - mock_DBot_store?.dashboard?.setHasTourEnded(true); - }); - render(, { - wrapper, - }); - expect(screen.getByText(/Want to retake the tour?/i)).toBeInTheDocument(); - }); - - it('should start onboarding tour', () => { - act(() => { - mock_DBot_store?.dashboard?.setActiveTab(1); - mock_DBot_store?.dashboard?.setTourDialogVisibility(true); - mock_DBot_store?.dashboard?.setHasTourEnded(false); - }); - render(, { - wrapper, - }); - - act(() => { - const buttonElement = screen.getByText('Start', { selector: 'span' }); - fireEvent.click(buttonElement); - }); - - expect(screen.getByText('OK')).toBeInTheDocument(); - }); - - it('should show tour success message', () => { - act(() => { - mock_DBot_store?.dashboard?.setTourDialogVisibility(true); - mock_DBot_store?.dashboard?.setActiveTab(1); - mock_DBot_store?.dashboard?.setHasTourEnded(true); - }); - render(, { - wrapper, - }); - expect(screen.getByTestId('tour-success-message')).toBeInTheDocument(); - }); - - it('should cancel tour', () => { - act(() => { - mock_DBot_store?.dashboard?.setTourDialogVisibility(true); - mock_DBot_store?.dashboard?.setActiveTab(1); - mock_DBot_store?.dashboard?.setHasTourEnded(true); - }); - render(, { - wrapper, - }); - - act(() => { - const buttonElement = screen.getByText('Skip', { selector: 'span' }); - fireEvent.click(buttonElement); - }); - - expect(mock_DBot_store?.dashboard?.is_tour_dialog_visible).toBeFalsy(); - }); - - it('should render bot builder tour', () => { - act(() => { - mock_DBot_store?.dashboard?.setActiveTab(1); - mock_DBot_store?.dashboard?.setTourDialogVisibility(true); - mock_DBot_store?.dashboard?.setHasTourEnded(false); - }); - render(, { - wrapper, - }); - expect(screen.getByText("Let's build a Bot!")).toBeInTheDocument(); - }); - - it('should start bot builder tour', () => { - act(() => { - setTourType('bot_builder'); - mock_DBot_store?.dashboard?.setActiveTab(2); - mock_DBot_store?.dashboard?.setTourDialogVisibility(true); - mock_DBot_store?.dashboard?.setHasTourEnded(false); - }); - render(, { - wrapper, - }); - - act(() => { - const buttonElement = screen.getByText('Start', { selector: 'span' }); - fireEvent.click(buttonElement); - }); - - expect(screen.getByText('OK')).toBeInTheDocument(); - }); - - it('should exit bot builder tour', () => { - act(() => { - setTourType('bot_builder'); - mock_DBot_store?.dashboard?.setActiveTab(2); - mock_DBot_store?.dashboard?.setTourDialogVisibility(true); - mock_DBot_store?.dashboard?.setHasTourEnded(true); - }); - render(, { - wrapper, - }); - - act(() => { - const buttonElement = screen.getByText('Skip', { selector: 'span' }); - fireEvent.click(buttonElement); - }); - - expect(mock_DBot_store?.dashboard?.is_tour_dialog_visible).toBeFalsy(); - }); -}); diff --git a/packages/bot-web-ui/src/components/dashboard/tour-trigger-dialog.tsx b/packages/bot-web-ui/src/components/dashboard/tour-trigger-dialog.tsx deleted file mode 100644 index d20903539ec2..000000000000 --- a/packages/bot-web-ui/src/components/dashboard/tour-trigger-dialog.tsx +++ /dev/null @@ -1,171 +0,0 @@ -import React from 'react'; -import classNames from 'classnames'; -import { Dialog, Text } from '@deriv/components'; -import { isMobile } from '@deriv/shared'; -import { observer } from '@deriv/stores'; -import { Localize, localize } from '@deriv/translations'; -import { useDBotStore } from '../../stores/useDBotStore'; -import { setTourSettings, tour_status_ended, tour_type } from './joyride-config'; - -const TourTriggrerDialog = observer(() => { - const { dashboard } = useDBotStore(); - const { active_tab, has_tour_ended, is_tour_dialog_visible, setTourDialogVisibility, toggleOnConfirm } = dashboard; - - const is_mobile = isMobile(); - - const toggleTour = (value: boolean, type: string) => { - if (tour_type.key === 'onboard_tour') { - if (type === 'onConfirm') { - toggleOnConfirm(active_tab, value); - } else { - setTourSettings(new Date().getTime(), `${tour_type.key}_token`); - } - tour_type.key = 'onboard_tour'; - } else if (tour_type.key === 'bot_builder') { - if (type === 'onConfirm') { - toggleOnConfirm(active_tab, value); - } else { - setTourSettings(new Date().getTime(), `${tour_type.key}_token`); - } - tour_type.key = 'bot_builder'; - } - setTourDialogVisibility(false); - }; - - const dashboardTourContent = () => { - if (!has_tour_ended) { - return ( - ]} - /> - ); - } - return ( - Tutorials.'} components={[]} /> - ); - }; - - const getTourHeaders = (tour_check: boolean, tab_id: number) => { - let text; - if (!tour_check) { - if (tab_id === 1 && is_mobile) text = localize('Bot Builder guide'); - else if (tab_id === 1) text = localize("Let's build a Bot!"); - else text = localize('Get started on Deriv Bot'); - } else if (tab_id === 1) text = localize('Congratulations'); - else text = localize('Want to retake the tour?'); - return text; - }; - - const tourDialogInfo = is_mobile - ? localize('Here’s a quick guide on how to use Deriv Bot on the go.') - : localize('Learn how to build your bot from scratch using a simple strategy.'); - - const tourDialogAction = is_mobile - ? localize( - 'You can import a bot from your mobile device or from Google drive, see a preview in the bot builder, and start trading by running the bot.' - ) - : localize('Hit the <0>Start button to begin and follow the tutorial.'); - - const getTourContent = (type: string) => { - return ( - <> - {type === 'header' && getTourHeaders(has_tour_ended, active_tab)} - {type === 'content' && active_tab === 0 && dashboardTourContent()} - {type === 'content' && - active_tab === 1 && - (!has_tour_ended ? ( - <> -
- -
-
- ]} - /> -
-
- Tutorials tab.' - } - components={[]} - /> -
- - ) : ( - <> -
- -
-
- run the bot to test out the strategy.'} - components={[]} - /> -
-
- Tutorials tab.' - } - components={[]} - /> -
- - ))} - - ); - }; - - const confirm_button = active_tab === 0 ? localize('Got it, thanks!') : localize('OK'); - - const onHandleConfirm = React.useCallback(() => { - const status = tour_status_ended.key === 'finished'; - toggleTour(status ? false : !has_tour_ended, 'onConfirm'); - tour_status_ended.key = ''; - return status ? tour_status_ended.key : null; - }, [has_tour_ended, active_tab]); - - return ( -
- toggleTour(false, 'onCancel')} - confirm_button_text={has_tour_ended ? confirm_button : localize('Start')} - onConfirm={onHandleConfirm} - is_mobile_full_width - className={classNames('dc-dialog', { - 'tour-dialog': active_tab === 0 || active_tab === 1, - 'tour-dialog--end': (active_tab === 0 || active_tab === 1) && has_tour_ended, - })} - has_close_icon={false} - > -
- - {is_tour_dialog_visible && getTourContent('header')} - -
-
- - {is_tour_dialog_visible && getTourContent('content')} - -
-
-
- ); -}); - -export default TourTriggrerDialog; diff --git a/packages/bot-web-ui/src/components/dashboard/tutorial-tab/guide-content.tsx b/packages/bot-web-ui/src/components/dashboard/tutorial-tab/guide-content.tsx index 7f5486abc4d0..f7c64e63cf9c 100644 --- a/packages/bot-web-ui/src/components/dashboard/tutorial-tab/guide-content.tsx +++ b/packages/bot-web-ui/src/components/dashboard/tutorial-tab/guide-content.tsx @@ -7,7 +7,7 @@ import { localize } from '@deriv/translations'; import { DBOT_TABS } from 'Constants/bot-contents'; import { useDBotStore } from 'Stores/useDBotStore'; import { removeKeyValue } from '../../../utils/settings'; -import { tour_type } from '../joyride-config'; +import { tour_type } from '../dbot-tours/utils'; type TGuideContent = { guide_list: []; diff --git a/packages/bot-web-ui/src/stores/dashboard-store.ts b/packages/bot-web-ui/src/stores/dashboard-store.ts index ce69e3804cd4..2446bebab8a3 100644 --- a/packages/bot-web-ui/src/stores/dashboard-store.ts +++ b/packages/bot-web-ui/src/stores/dashboard-store.ts @@ -2,7 +2,7 @@ import { action, computed, makeObservable, observable, reaction } from 'mobx'; import { setColors } from '@deriv/bot-skeleton'; import { isMobile } from '@deriv/shared'; import { clearInjectionDiv } from 'Constants/load-modal'; -import { setTourSettings, tour_type, TTourType } from '../components/dashboard/joyride-config'; +import { setTourSettings, tour_type, TTourType } from '../components/dashboard/dbot-tours/utils'; import RootStore from './root-store'; export interface IDashboardStore { diff --git a/packages/bot-web-ui/src/stores/save-modal-store.ts b/packages/bot-web-ui/src/stores/save-modal-store.ts index 0de8819576ee..94b1863fc4cc 100644 --- a/packages/bot-web-ui/src/stores/save-modal-store.ts +++ b/packages/bot-web-ui/src/stores/save-modal-store.ts @@ -160,6 +160,8 @@ export default class SaveModalStore implements ISaveModalStore { this.setButtonStatus(button_status.COMPLETED); } + this.updateBotName(bot_name); + if (active_tab === 0) { const workspace_id = selected_strategy.id || Blockly?.utils?.genUid(); await this.addStrategyToWorkspace(workspace_id, is_local, save_as_collection, bot_name, xml); @@ -167,7 +169,6 @@ export default class SaveModalStore implements ISaveModalStore { } else { await saveWorkspaceToRecent(xml, is_local ? save_types.LOCAL : save_types.GOOGLE_DRIVE); } - this.updateBotName(bot_name); this.toggleSaveModal(); } diff --git a/packages/cashier/src/pages/account-transfer/account-transfer-form/account-transfer-form-side-note.tsx b/packages/cashier/src/pages/account-transfer/account-transfer-form/account-transfer-form-side-note.tsx index f2eaaa2da1e3..ca240ded84f6 100644 --- a/packages/cashier/src/pages/account-transfer/account-transfer-form/account-transfer-form-side-note.tsx +++ b/packages/cashier/src/pages/account-transfer/account-transfer-form/account-transfer-form-side-note.tsx @@ -8,6 +8,7 @@ type TAccountTransferNoteProps = { allowed_transfers_count: GetLimits['daily_transfers']; currency: string; is_crypto_to_crypto_transfer?: boolean; + is_ctrader_transfer?: boolean; is_derivez_transfer?: boolean; is_dxtrade_allowed: boolean; is_dxtrade_transfer?: boolean; diff --git a/packages/cashier/src/pages/account-transfer/account-transfer-form/account-transfer-form.tsx b/packages/cashier/src/pages/account-transfer/account-transfer-form/account-transfer-form.tsx index 332f23d59f63..fb3707c2ec34 100644 --- a/packages/cashier/src/pages/account-transfer/account-transfer-form/account-transfer-form.tsx +++ b/packages/cashier/src/pages/account-transfer/account-transfer-form/account-transfer-form.tsx @@ -33,6 +33,8 @@ type TAccountTransferFormProps = { }; const AccountOption = ({ account, idx }: TAccountsList) => { + const is_cfd_account = account.is_dxtrade || account.is_ctrader || account.is_mt || account.is_derivez; + return ( {(account.currency || account.platform_icon) && ( @@ -43,9 +45,7 @@ const AccountOption = ({ account, idx }: TAccountsList) => {
- {account.is_dxtrade || account.is_mt || account.is_derivez - ? account.text - : getCurrencyName(account.currency)} + {!is_cfd_account ? getCurrencyName(account.currency) : account.text} {!account.is_derivez && ( @@ -68,6 +68,8 @@ const AccountOption = ({ account, idx }: TAccountsList) => { let accounts_from: TAccount[] = []; let accounts_to: TAccount[] = []; +let ctrader_accounts_from: TAccount[] = []; +let ctrader_accounts_to: TAccount[] = []; let derivez_accounts_from: TAccount[] = []; let derivez_accounts_to: TAccount[] = []; let dxtrade_accounts_from: TAccount[] = []; @@ -123,11 +125,13 @@ const AccountTransferForm = observer( const { daily_transfers } = account_limits; const mt5_remaining_transfers = daily_transfers?.mt5; + const ctrader_remaining_transfers = daily_transfers?.ctrader; const dxtrade_remaining_transfers = daily_transfers?.dxtrade; const derivez_remaining_transfers = daily_transfers?.derivez; const internal_remaining_transfers = daily_transfers?.internal; const is_mt_transfer = selected_to.is_mt || selected_from.is_mt; + const is_ctrader_transfer = selected_to.is_ctrader || selected_from.is_ctrader; const is_dxtrade_transfer = selected_to.is_dxtrade || selected_from.is_dxtrade; const is_derivez_transfer = selected_to.is_derivez || selected_from.is_derivez; @@ -156,15 +160,17 @@ const AccountTransferForm = observer( return selected_from.currency === selected_to.currency ? !amount : !converter_from_amount; }; - const getAccounts = (type: string, { is_mt, is_dxtrade, is_derivez }: TAccount) => { + const getAccounts = (type: string, { is_mt, is_ctrader, is_dxtrade, is_derivez }: TAccount) => { if (type === 'from') { if (is_mt) return mt_accounts_from; + if (is_ctrader) return ctrader_accounts_from; if (is_dxtrade) return dxtrade_accounts_from; if (is_derivez) return derivez_accounts_from; return accounts_from; } else if (type === 'to') { if (is_mt) return mt_accounts_to; + if (is_ctrader) return ctrader_accounts_to; if (is_dxtrade) return dxtrade_accounts_to; if (is_derivez) return derivez_accounts_to; @@ -180,10 +186,12 @@ const AccountTransferForm = observer( React.useEffect(() => { accounts_from = []; mt_accounts_from = []; + ctrader_accounts_from = []; dxtrade_accounts_from = []; derivez_accounts_from = []; accounts_to = []; mt_accounts_to = []; + ctrader_accounts_to = []; dxtrade_accounts_to = []; derivez_accounts_to = []; @@ -191,12 +199,12 @@ const AccountTransferForm = observer( const text = ; const value = account.value; - const is_cfd_account = account.is_mt || account.is_dxtrade || account.is_derivez; - + const is_cfd_account = account.is_mt || account.is_ctrader || account.is_dxtrade || account.is_derivez; getAccounts('from', account).push({ text, value, is_mt: account.is_mt, + is_ctrader: account.is_ctrader, is_dxtrade: account.is_dxtrade, nativepicker_text: `${is_cfd_account ? account.market_type : getCurrencyName(account.currency)} (${ account.balance @@ -204,22 +212,30 @@ const AccountTransferForm = observer( }); const is_selected_from = account.value === selected_from.value; - if ((selected_from.is_mt && account.is_dxtrade) || (selected_from.is_dxtrade && account.is_mt)) return; + if ( + (selected_from.is_mt && (account.is_dxtrade || account.is_ctrader)) || + (selected_from.is_dxtrade && (account.is_mt || account.is_ctrader)) || + (selected_from.is_ctrader && (account.is_mt || account.is_dxtrade)) + ) + return; // account from and to cannot be the same if (!is_selected_from) { const is_selected_from_mt = selected_from.is_mt && account.is_mt; + const is_selected_from_ctrader = selected_from.is_ctrader && account.is_ctrader; const is_selected_from_dxtrade = selected_from.is_dxtrade && account.is_dxtrade; // cannot transfer to MT account from MT + // cannot transfer to cTrader account from cTrader // cannot transfer to Dxtrade account from Dxtrade - const is_disabled = is_selected_from_mt || is_selected_from_dxtrade; + const is_disabled = is_selected_from_mt || is_selected_from_ctrader || is_selected_from_dxtrade; getAccounts('to', account).push({ text, value, is_mt: account.is_mt, + is_ctrader: account.is_ctrader, is_dxtrade: account.is_dxtrade, is_derivez: account.is_derivez, disabled: is_disabled, @@ -232,6 +248,7 @@ const AccountTransferForm = observer( setFromAccounts({ ...(mt_accounts_from.length && { [localize('Deriv MT5 accounts')]: mt_accounts_from }), + ...(ctrader_accounts_from.length && { [localize('Deriv cTrader accounts')]: ctrader_accounts_from }), ...(dxtrade_accounts_from.length && { [localize('{{platform_name_dxtrade}} accounts', { platform_name_dxtrade })]: dxtrade_accounts_from, }), @@ -241,6 +258,7 @@ const AccountTransferForm = observer( setToAccounts({ ...(mt_accounts_to.length && { [localize('Deriv MT5 accounts')]: mt_accounts_to }), + ...(ctrader_accounts_to.length && { [localize('Deriv cTrader accounts')]: ctrader_accounts_to }), ...(dxtrade_accounts_to.length && { [localize('{{platform_name_dxtrade}} accounts', { platform_name_dxtrade })]: dxtrade_accounts_to, }), @@ -257,6 +275,7 @@ const AccountTransferForm = observer( allowed_transfers_count={{ internal: internal_remaining_transfers?.allowed, mt5: mt5_remaining_transfers?.allowed, + ctrader: ctrader_remaining_transfers?.allowed, dxtrade: dxtrade_remaining_transfers?.allowed, derivez: derivez_remaining_transfers?.allowed, }} @@ -268,6 +287,7 @@ const AccountTransferForm = observer( is_dxtrade_allowed={is_dxtrade_allowed} is_dxtrade_transfer={is_dxtrade_transfer} is_mt_transfer={is_mt_transfer} + is_ctrader_transfer={is_ctrader_transfer} is_from_derivgo={is_from_derivgo} is_derivez_transfer={is_derivez_transfer} /> @@ -299,12 +319,16 @@ const AccountTransferForm = observer( is_mt_transfer, is_from_derivgo, is_derivez_transfer, + ctrader_remaining_transfers?.allowed, + is_ctrader_transfer, ]); React.useEffect(() => { const getRemainingTransfers = () => { if (is_mt_transfer) { return mt5_remaining_transfers?.available; + } else if (is_ctrader_transfer) { + return ctrader_remaining_transfers?.available; } else if (is_dxtrade_transfer) { return dxtrade_remaining_transfers?.available; } else if (is_derivez_transfer) { @@ -616,6 +640,7 @@ const AccountTransferForm = observer( allowed_transfers_count={{ internal: internal_remaining_transfers?.allowed, mt5: mt5_remaining_transfers?.allowed, + ctrader: ctrader_remaining_transfers?.allowed, dxtrade: dxtrade_remaining_transfers?.allowed, derivez: derivez_remaining_transfers?.allowed, }} @@ -627,6 +652,7 @@ const AccountTransferForm = observer( } is_dxtrade_allowed={is_dxtrade_allowed} is_dxtrade_transfer={is_dxtrade_transfer} + is_ctrader_transfer={is_ctrader_transfer} is_mt_transfer={is_mt_transfer} is_from_derivgo={is_from_derivgo} is_derivez_transfer={is_derivez_transfer} diff --git a/packages/cashier/src/pages/account-transfer/account-transfer-receipt/account-transfer-receipt.tsx b/packages/cashier/src/pages/account-transfer/account-transfer-receipt/account-transfer-receipt.tsx index 255346fa58ae..50898ab92428 100644 --- a/packages/cashier/src/pages/account-transfer/account-transfer-receipt/account-transfer-receipt.tsx +++ b/packages/cashier/src/pages/account-transfer/account-transfer-receipt/account-transfer-receipt.tsx @@ -58,7 +58,7 @@ const AccountTransferReceipt = observer(({ onClose, history }: TAccountTransferR // the account transferred to is a Deriv MT5 account that can't be switched to and from account is your logged in account if ( selected_to.value === loginid || - ((selected_to.is_mt || selected_to.is_dxtrade) && selected_from.value === loginid) + ((selected_to.is_mt || selected_to.is_dxtrade || selected_to.is_ctrader) && selected_from.value === loginid) ) { openStatement(); } else { diff --git a/packages/cashier/src/stores/account-transfer-store.ts b/packages/cashier/src/stores/account-transfer-store.ts index 05f47f49dd20..87c477c0d265 100644 --- a/packages/cashier/src/stores/account-transfer-store.ts +++ b/packages/cashier/src/stores/account-transfer-store.ts @@ -94,7 +94,7 @@ export default class AccountTransferStore { account_status, } = this.root_store.client; - if (!account_status.status) return false; + if (!account_status?.status) return false; const need_financial_assessment = is_financial_account && (is_financial_information_incomplete || is_trading_experience_incomplete); @@ -234,20 +234,14 @@ export default class AccountTransferStore { } setTransferLimit() { - const is_mt_transfer = this.selected_from.is_mt || this.selected_to.is_mt; - const is_dxtrade_transfer = this.selected_from.is_dxtrade || this.selected_to.is_dxtrade; - const is_derivez_transfer = this.selected_from.is_derivez || this.selected_to.is_derivez; - - let limits_key; - if (is_mt_transfer) { - limits_key = 'limits_mt5'; - } else if (is_dxtrade_transfer) { - limits_key = 'limits_dxtrade'; - } else if (is_derivez_transfer) { - limits_key = 'limits_derivez'; - } else { - limits_key = 'limits'; - } + const transfer_limits = [ + { limits: 'limits_mt5', is_transfer: this.selected_from.is_mt || this.selected_to.is_mt }, + { limits: 'limits_dxtrade', is_transfer: this.selected_from.is_dxtrade || this.selected_to.is_dxtrade }, + { limits: 'limits_ctrader', is_transfer: this.selected_from.is_ctrader || this.selected_to.is_ctrader }, + { limits: 'limits_derivez', is_transfer: this.selected_from.is_derivez || this.selected_to.is_derivez }, + ]; + + const limits_key = transfer_limits.find(option => option.is_transfer)?.limits || 'limits'; const transfer_limit = getPropertyValue(getCurrencies(), [ this.selected_from.currency || '', @@ -290,6 +284,9 @@ export default class AccountTransferStore { const dxtrade_accounts_list = (await this.WS.tradingPlatformAccountsList(CFD_PLATFORMS.DXTRADE)) ?.trading_platform_accounts; + const ctrader_accounts_list = (await this.WS.tradingPlatformAccountsList(CFD_PLATFORMS.CTRADER)) + ?.trading_platform_accounts; + const derivez_accounts_list = (await this.WS.tradingPlatformAccountsList(CFD_PLATFORMS.DERIVEZ)) ?.trading_platform_accounts; @@ -305,6 +302,17 @@ export default class AccountTransferStore { return { ...account, ...found_account, account_type: CFD_PLATFORMS.MT5 }; } + if ( + account.account_type === CFD_PLATFORMS.CTRADER && + Array.isArray(ctrader_accounts_list) && + ctrader_accounts_list.length + ) { + const found_account = ctrader_accounts_list.find(acc => acc.account_id === account.loginid); + + if (found_account === undefined) return account; + + return { ...account, ...found_account, account_type: CFD_PLATFORMS.CTRADER }; + } if ( account.account_type === CFD_PLATFORMS.DXTRADE && Array.isArray(dxtrade_accounts_list) && @@ -373,6 +381,7 @@ export default class AccountTransferStore { accounts?.forEach((account: TTransferAccount) => { const cfd_platforms = { mt5: { name: 'Deriv MT5', icon: 'IcMt5' }, + ctrader: { name: 'Deriv cTrader', icon: 'IcBrand' }, dxtrade: { name: 'Deriv X', icon: 'IcRebranding' }, derivez: { name: 'Deriv EZ', icon: 'IcRebranding' }, }; @@ -387,6 +396,7 @@ export default class AccountTransferStore { sub_account_type: account.sub_account_type, platform: account.account_type, is_eu: this.root_store.client.is_eu, + is_transfer_form: true, })) || '' }`; @@ -406,13 +416,15 @@ export default class AccountTransferStore { platform: account.account_type, is_eu: this.root_store.client.is_eu, })} ${this.root_store.client.is_eu ? '' : non_eu_accounts}` - : `${cfd_text_display} ${getCFDAccountDisplay({ - market_type: account.market_type, - sub_account_type: account.sub_account_type, - platform: account.account_type, - is_eu: this.root_store.client.is_eu, - is_transfer_form: true, - })}`; + : `${cfd_text_display} ${ + getCFDAccountDisplay({ + market_type: account.market_type, + sub_account_type: account.sub_account_type, + platform: account.account_type, + is_eu: this.root_store.client.is_eu, + is_transfer_form: true, + }) || '' + }`; const account_text_display = is_cfd ? cfd_account_text_display : getCurrencyDisplayCode( @@ -437,6 +449,7 @@ export default class AccountTransferStore { currency: account.currency, is_crypto: isCryptocurrency(account.currency), is_mt: account.account_type === CFD_PLATFORMS.MT5, + is_ctrader: account.account_type === CFD_PLATFORMS.CTRADER, is_dxtrade: account.account_type === CFD_PLATFORMS.DXTRADE, is_derivez: account.account_type === CFD_PLATFORMS.DERIVEZ, ...(is_cfd && { @@ -645,6 +658,17 @@ export default class AccountTransferStore { setBalanceOtherAccounts(balance_response.balance); }); } + // if one of the accounts was ctrader + if (account.account_type === CFD_PLATFORMS.CTRADER) { + Promise.all([ + this.WS.tradingPlatformAccountsList(CFD_PLATFORMS.CTRADER), + this.WS.balanceAll(), + ]).then(([ctrader_login_list_response, balance_response]) => { + // update the balance for account switcher by renewing the ctrader_login_list_response + responseTradingPlatformAccountsList(ctrader_login_list_response); + setBalanceOtherAccounts(balance_response.balance); + }); + } }); this.setAccountTransferAmount(null); this.setIsTransferConfirm(true); diff --git a/packages/cashier/src/types/account.types.ts b/packages/cashier/src/types/account.types.ts index e5cae51479b1..c74f156952db 100644 --- a/packages/cashier/src/types/account.types.ts +++ b/packages/cashier/src/types/account.types.ts @@ -11,6 +11,7 @@ export type TAccount = { disabled?: boolean; error?: JSX.Element | string; is_crypto?: boolean; + is_ctrader?: boolean; is_derivez?: boolean; is_dxtrade?: boolean; is_mt?: boolean; diff --git a/packages/cfd/build/webpack.config.js b/packages/cfd/build/webpack.config.js index 83625342aa01..f9e9569e0154 100644 --- a/packages/cfd/build/webpack.config.js +++ b/packages/cfd/build/webpack.config.js @@ -22,6 +22,7 @@ module.exports = function (env) { CFDPasswordManagerModal: 'Containers/cfd-password-manager-modal.tsx', CFDFinancialStpRealAccountSignup: 'Containers/cfd-financial-stp-real-account-signup.tsx', getDXTradeWebTerminalLink: 'Helpers/constants.ts', + getCTraderWebTerminalLink: 'Helpers/constants.ts', getDerivEzWebTerminalLink: 'Helpers/constants.ts', }, mode: IS_RELEASE ? 'production' : 'development', diff --git a/packages/cfd/src/Assets/svgs/trading-platform/ic-appstore-ctrader.svg b/packages/cfd/src/Assets/svgs/trading-platform/ic-appstore-ctrader.svg new file mode 100644 index 000000000000..265b5c52d55b --- /dev/null +++ b/packages/cfd/src/Assets/svgs/trading-platform/ic-appstore-ctrader.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/cfd/src/Assets/svgs/trading-platform/index.tsx b/packages/cfd/src/Assets/svgs/trading-platform/index.tsx index 3cce434188af..2909e54e757b 100644 --- a/packages/cfd/src/Assets/svgs/trading-platform/index.tsx +++ b/packages/cfd/src/Assets/svgs/trading-platform/index.tsx @@ -2,6 +2,7 @@ import React from 'react'; import Derived from './ic-appstore-derived.svg'; import Financial from './ic-appstore-financial.svg'; import CFDs from './ic-appstore-cfds.svg'; +import CTrader from './ic-appstore-ctrader.svg'; import DerivEz from './ic-appstore-derivez.svg'; import SwapFree from './ic-appstore-swap-free.svg'; import DerivX from './ic-appstore-deriv-x.svg'; @@ -17,6 +18,7 @@ export const PlatformIcons = { Derived, Financial, CFDs, + CTrader, DerivEz, SwapFree, DerivX, diff --git a/packages/cfd/src/Components/__tests__/cfd-account-card.spec.tsx b/packages/cfd/src/Components/__tests__/cfd-account-card.spec.tsx index 37a7bf032e4d..06af4cab76d6 100644 --- a/packages/cfd/src/Components/__tests__/cfd-account-card.spec.tsx +++ b/packages/cfd/src/Components/__tests__/cfd-account-card.spec.tsx @@ -333,7 +333,8 @@ describe('CFDAccountCard', () => { />, renderOptions ); - fireEvent.click(screen.getByText(/add real account/i)); + const add_real_account_buttons = screen.getAllByRole('button', { name: /add real account/i }); + fireEvent.click(add_real_account_buttons[0]); expect(props.onSelectAccount).toHaveBeenCalled(); }); @@ -355,7 +356,8 @@ describe('CFDAccountCard', () => { />, renderOptions ); - fireEvent.click(screen.getByText(/add demo account/i)); + const add_demo_account_buttons = screen.getAllByRole('button', { name: /add demo account/i }); + fireEvent.click(add_demo_account_buttons[0]); expect(props.onSelectAccount).toHaveBeenCalled(); }); diff --git a/packages/cfd/src/Components/__tests__/cfd-demo-account-display.spec.js b/packages/cfd/src/Components/__tests__/cfd-demo-account-display.spec.js index ea294b0c7737..f41309303670 100644 --- a/packages/cfd/src/Components/__tests__/cfd-demo-account-display.spec.js +++ b/packages/cfd/src/Components/__tests__/cfd-demo-account-display.spec.js @@ -133,12 +133,12 @@ describe('', () => { checkAccountCardsRendering(TESTED_CASES.NON_EU_DMT5); const add_demo_account_buttons = screen.getAllByRole('button', { name: /add demo account/i }); - expect(add_demo_account_buttons.length).toBe(2); + expect(add_demo_account_buttons.length).toBe(4); fireEvent.click(add_demo_account_buttons[0]); expect(props.onSelectAccount).toHaveBeenCalledWith({ type: 'synthetic', category: 'demo', platform: 'mt5' }); - fireEvent.click(add_demo_account_buttons[1]); + fireEvent.click(add_demo_account_buttons[2]); expect(props.onSelectAccount).toHaveBeenCalledWith({ type: 'financial', category: 'demo', platform: 'mt5' }); }); @@ -159,10 +159,10 @@ describe('', () => { }); checkAccountCardsRendering(TESTED_CASES.EU); - const add_demo_account_button = screen.getByRole('button', { name: /add demo account/i }); - expect(add_demo_account_button).toBeEnabled(); + const add_demo_account_button = screen.getAllByRole('button', { name: /add demo account/i }); + expect(add_demo_account_button[0]).toBeEnabled(); - fireEvent.click(add_demo_account_button); + fireEvent.click(add_demo_account_button[0]); expect(props.openAccountNeededModal).toHaveBeenCalledWith('maltainvest', 'Deriv Multipliers', 'demo CFDs'); }); @@ -183,7 +183,7 @@ describe('', () => { checkAccountCardsRendering(TESTED_CASES.NON_EU_DXTRADE); const add_demo_account_buttons = screen.getAllByRole('button', { name: /add demo account/i }); - expect(add_demo_account_buttons.length).toBe(2); + expect(add_demo_account_buttons.length).toBe(4); fireEvent.click(add_demo_account_buttons[0]); expect(props.onSelectAccount).toHaveBeenCalledWith({ @@ -192,7 +192,7 @@ describe('', () => { platform: 'dxtrade', }); - fireEvent.click(add_demo_account_buttons[1]); + fireEvent.click(add_demo_account_buttons[2]); expect(props.onSelectAccount).toHaveBeenCalledWith({ type: 'financial', category: 'demo', diff --git a/packages/cfd/src/Components/__tests__/cfd-poa.spec.js b/packages/cfd/src/Components/__tests__/cfd-poa.spec.js deleted file mode 100644 index 802897bcc797..000000000000 --- a/packages/cfd/src/Components/__tests__/cfd-poa.spec.js +++ /dev/null @@ -1,261 +0,0 @@ -import React from 'react'; -import { fireEvent, render, screen, waitFor } from '@testing-library/react'; -import CFDPOA from '../cfd-poa'; -import { BrowserRouter } from 'react-router-dom'; -import { WS, isDesktop, isMobile } from '@deriv/shared'; - -const poa_status_codes = { - none: 'none', - pending: 'pending', - rejected: 'rejected', - verified: 'verified', - expired: 'expired', - suspected: 'suspected', -}; - -jest.mock('@deriv/shared', () => ({ - ...jest.requireActual('@deriv/shared'), - getLocation: jest.fn().mockReturnValue('Default test state'), - isDesktop: jest.fn(), - isMobile: jest.fn(), - makeCancellablePromise: jest.fn(() => ({ cancel: jest.fn(), promise: Promise.resolve('resolved') })), -})); - -jest.mock('@deriv/account', () => ({ - ...jest.requireActual('@deriv/account'), - FileUploaderContainer: () =>
FileUploaderContainer
, - FormSubHeader: jest.fn(props =>
{props.title}
), - PoaStatusCodes: jest.fn(() => { - poa_status_codes.none; - }), -})); - -jest.mock('@deriv/shared/src/services/ws-methods', () => ({ - __esModule: true, - default: 'mockedDefaultExport', - WS: { - authorized: { - getAccountStatus: jest.fn().mockResolvedValue({ - get_account_status: { - authentication: { - document: { - status: 'none', - }, - identity: { - status: 'none', - }, - }, - }, - }), - }, - wait: (...payload) => { - return Promise.resolve([...payload]); - }, - }, -})); - -const renderwithRouter = component => { - render({component}); -}; - -const error_messages = { - address_line_1: 'First line of address is required', - town_city: 'Town/City is required.', -}; - -describe('', () => { - const address = { - address_line_1: 'First line of address*', - address_line_2: 'Second line of address (optional)', - address_postcode: 'Postal/ZIP code', - address_state: 'State/Province', - address_town: 'Town/City*', - }; - - let modal_root_el; - - beforeAll(() => { - modal_root_el = document.createElement('div'); - modal_root_el.setAttribute('id', 'modal_root'); - document.body.appendChild(modal_root_el); - }); - - afterAll(() => { - document.body.removeChild(modal_root_el); - }); - - let props; - - beforeEach(() => { - props = { - onSave: jest.fn(), - index: 1, - onSubmit: jest.fn(), - refreshNotifications: jest.fn(), - form_error: undefined, - get_settings: { - account_opening_reason: '', - address_city: 'Woodlands', - address_line_1: "69 Test Street, .'", - address_line_2: ".'", - address_postcode: '666', - address_state: '', - allow_copiers: 0, - citizen: '', - client_tnc_status: 'Version 4.2.0 2020-08-07', - country: 'Singapore', - country_code: 'sg', - date_of_birth: 984960000, - email: 'mock@gmail.com', - email_consent: 1, - feature_flag: { - wallet: 0, - }, - first_name: 'thisyahlen', - has_secret_answer: 1, - immutable_fields: ['residence'], - is_authenticated_payment_agent: 0, - last_name: 'lol', - non_pep_declaration: 1, - phone: '+790875616', - place_of_birth: null, - preferred_language: 'EN', - request_professional_status: 0, - residence: 'Singapore', - salutation: '', - tax_identification_number: null, - tax_residence: null, - user_hash: '823341c18bfccb391b6bb5d77ab7e6a83991f82669c1ba4e5b01dbd2fd71c7fe', - }, - height: 'auto', - is_loading: false, - states_list: { - text: 'Central Singapore', - value: '01', - }, - storeProofOfAddress: jest.fn(), - value: { - address_line_1: '', - address_line_2: '', - address_city: '', - address_postcode: '', - address_state: 'Default test state', - }, - }; - }); - - it('should render the POA component form', async () => { - renderwithRouter(); - - expect(await screen.findByText(/Address information/i)).toBeInTheDocument(); - - expect(screen.getByText(/First line of address*/i)).toBeInTheDocument(); - expect(screen.getByText(/Second line of address \(optional\)/i)).toBeInTheDocument(); - expect(screen.getByText('Town/City*')).toBeInTheDocument(); - expect(screen.getByText('State/Province')).toBeInTheDocument(); - expect(screen.getByText('Postal/ZIP code')).toBeInTheDocument(); - expect(screen.getByText(/FileUploaderContainer/i)).toBeInTheDocument(); - expect(screen.getByRole('button', { name: /Next/i })).toBeInTheDocument(); - }); - - it('should disable the next button if there are no values', async () => { - renderwithRouter(); - expect(await screen.findByText(/Address information/i)).toBeInTheDocument(); - - const next_btn = screen.getByRole('button', { name: /Next/i }); - expect(next_btn.disabled).toBe(true); - }); - - it('should render the correct input values', async () => { - const new_props = { - ...props, - value: { - address_line_1: 'dead end', - address_line_2: 'Psycho Path', - address_city: 'hells kitchen', - address_postcode: '666', - address_state: 'alabama', - }, - }; - renderwithRouter(); - - expect(await screen.findByText(/Address information/i)).toBeInTheDocument(); - - const address_line_1_input = screen.getByLabelText(address.address_line_1); - const address_line_2_input = screen.getByLabelText(address.address_line_2); - const address_town_input = screen.getByLabelText(address.address_town); - const address_state_input = screen.getByLabelText(address.address_state); - const address_postcode_input = screen.getByLabelText(address.address_postcode); - - expect(address_line_1_input.value).toBe('dead end'); - expect(address_line_2_input.value).toBe('Psycho Path'); - expect(address_town_input.value).toBe('hells kitchen'); - expect(address_state_input.value).toBe('alabama'); - expect(address_postcode_input.value).toBe('666'); - }); - - it('should have validation errors on form if fields are empty', async () => { - renderwithRouter(); - - expect(await screen.findByText(/Address information/i)).toBeInTheDocument(); - - const address_line_1_input = screen.getByLabelText(address.address_line_1); - const address_town_input = screen.getByLabelText(address.address_town); - - fireEvent.blur(address_line_1_input); - fireEvent.blur(address_town_input); - - await waitFor(() => { - expect(screen.getByText(error_messages.address_line_1)).toBeInTheDocument(); - expect(screen.getByText(error_messages.town_city)).toBeInTheDocument(); - }); - }); - - it('should render the fileuploader mock component', async () => { - render(); - expect(await screen.findByText(/Address information/i)).toBeInTheDocument(); - expect(screen.getByText(/FileUploaderContainer/i)).toBeInTheDocument(); - }); - - it('should render the error title if POA has failed', async () => { - WS.authorized.getAccountStatus.mockResolvedValue({ - get_account_status: { - authentication: { - document: { - status: 'rejected', - }, - identity: { - status: 'rejected', - }, - }, - }, - }); - render(); - expect(await screen.findByText(/Address information/i)).toBeInTheDocument(); - expect( - screen.getByText( - 'We were unable to verify your address with the details you provided. Please check and resubmit or choose a different document type.' - ) - ).toBeInTheDocument(); - }); - - it('should render CFDPOA component with states_list combobox for mobile', async () => { - isDesktop.mockReturnValue(false); - isMobile.mockReturnValue(true); - - props.states_list = [ - { text: 'State 1', value: 'State 1' }, - { text: 'State 2', value: 'State 2' }, - ]; - - render(); - expect(await screen.findByText(/Address information/i)).toBeInTheDocument(); - - const address_state_input = screen.getByRole('combobox'); - expect(address_state_input.value).toBe(''); - fireEvent.change(address_state_input, { target: { value: 'State 2' } }); - await waitFor(() => { - expect(address_state_input.value).toBe('State 2'); - }); - }); -}); diff --git a/packages/cfd/src/Components/__tests__/cfd-poa.spec.tsx b/packages/cfd/src/Components/__tests__/cfd-poa.spec.tsx new file mode 100644 index 000000000000..0f6926f31df2 --- /dev/null +++ b/packages/cfd/src/Components/__tests__/cfd-poa.spec.tsx @@ -0,0 +1,106 @@ +import React from 'react'; +import { BrowserRouter } from 'react-router-dom'; +import { render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { mockStore } from '@deriv/stores'; +import CFDPOA from '../cfd-poa'; +import CFDProviders from '../../cfd-providers'; + +jest.mock('@deriv/account/src/Components/forms/personal-details-form.jsx', () => + jest.fn(() =>
PersonalDetailsForm
) +); +jest.mock('@deriv/account/src/Components/poa/common-mistakes/common-mistake-examples', () => + jest.fn(() =>
CommonMistakeExamples
) +); +jest.mock('@deriv/account/src/Components/leave-confirm', () => jest.fn(() =>
LeaveConfirm
)); +jest.mock('@deriv/shared', () => ({ + ...jest.requireActual('@deriv/shared'), + validPostCode: jest.fn(() => true), + validLength: jest.fn(() => true), + validLetterSymbol: jest.fn(() => true), + validAddress: jest.fn(() => ({ + is_ok: true, + })), + WS: { + authorized: { + storage: { + getSettings: jest.fn().mockResolvedValue({ + get_settings: { + address_line_1: 'test address_line_1', + address_line_2: 'test address_line_2', + address_city: 'test address_city', + address_state: 'test address_state', + address_postcode: 'test address_postcode', + }, + }), + getAccountStatus: jest.fn().mockResolvedValue({ + get_account_status: { + authentication: { + document: { + status: 'none', + }, + identity: { + status: 'none', + }, + }, + }, + }), + }, + }, + setSettings: jest.fn(() => Promise.resolve({ error: '' })), + wait: jest.fn(() => Promise.resolve([])), + }, +})); + +describe('', () => { + const mock_props: React.ComponentProps = { + index: 0, + onSave: jest.fn(), + onSubmit: jest.fn(), + }; + const mock_store = mockStore({ + client: { + account_settings: { + address_line_1: 'test address_line_1', + address_line_2: 'test address_line_2', + address_city: 'test address_city', + address_state: 'test address_state', + address_postcode: 'test address_postcode', + }, + fetchResidenceList: jest.fn(() => Promise.resolve('')), + getChangeableFields: jest.fn(() => []), + }, + }); + + it('should render CFDPOA and trigger buttons', async () => { + render( + + + + + + ); + + expect(await screen.findByText('PersonalDetailsForm')).toBeInTheDocument(); + + const button = screen.getByRole('button'); + expect(button).toHaveTextContent('Continue'); + expect(button).toBeDisabled(); + + const uploader = screen.getByTestId('dt_file_upload_input'); + const file = new File(['test file'], 'test_file.png', { type: 'image/png' }); + + await waitFor(() => { + userEvent.upload(uploader, file); + }); + + expect(button).toBeEnabled(); + + userEvent.click(button); + + await waitFor(() => { + expect(mock_props.onSave).toHaveBeenCalled(); + expect(mock_props.onSubmit).toHaveBeenCalled(); + }); + }); +}); diff --git a/packages/cfd/src/Components/__tests__/cfd-poi.spec.js b/packages/cfd/src/Components/__tests__/cfd-poi.spec.js index c9a2f2620cbd..cb081f2cb04a 100644 --- a/packages/cfd/src/Components/__tests__/cfd-poi.spec.js +++ b/packages/cfd/src/Components/__tests__/cfd-poi.spec.js @@ -4,17 +4,14 @@ import CFDPOI from '../cfd-poi'; import CFDProviders from '../../cfd-providers'; import { mockStore } from '@deriv/stores'; -jest.mock('@deriv/account', () => ({ - ...jest.requireActual('@deriv/account'), - ProofOfIdentityContainerForMt5: () =>
ProofOfIdentityContainerForMt5
, -})); +jest.mock('@deriv/account/src/Sections/Verification/ProofOfIdentity/proof-of-identity-container-for-mt5.jsx', () => + jest.fn(() =>
ProofOfIdentityContainerForMt5
) +); describe('', () => { let props; let mockRootStore; - const ProofOfIdentityContainerForMt5 = 'ProofOfIdentityContainerForMt5'; - beforeEach(() => { mockRootStore = { client: { @@ -104,6 +101,6 @@ describe('', () => { render(, { wrapper: ({ children }) => {children}, }); - expect(screen.getByText(ProofOfIdentityContainerForMt5)).toBeInTheDocument(); + expect(screen.getByText('ProofOfIdentityContainerForMt5')).toBeInTheDocument(); }); }); diff --git a/packages/cfd/src/Components/__tests__/cfd-real-account-display.spec.js b/packages/cfd/src/Components/__tests__/cfd-real-account-display.spec.js index f29524d32fe8..6b37aba7e2c1 100644 --- a/packages/cfd/src/Components/__tests__/cfd-real-account-display.spec.js +++ b/packages/cfd/src/Components/__tests__/cfd-real-account-display.spec.js @@ -215,12 +215,12 @@ describe('', () => { checkAccountCardsRendering(TESTED_CASES.NON_EU_DMT5); const add_real_account_buttons = screen.getAllByRole('button', { name: /add real account/i }); - expect(add_real_account_buttons.length).toBe(2); + expect(add_real_account_buttons.length).toBe(4); fireEvent.click(add_real_account_buttons[0]); expect(props.onSelectAccount).toHaveBeenCalledWith({ type: 'synthetic', category: 'real', platform: 'mt5' }); - fireEvent.click(add_real_account_buttons[1]); + fireEvent.click(add_real_account_buttons[2]); expect(props.onSelectAccount).toHaveBeenCalledWith({ type: 'financial', category: 'real', platform: 'mt5' }); }); @@ -248,10 +248,10 @@ describe('', () => { ); checkAccountCardsRendering(TESTED_CASES.EU); - const add_real_account_button = screen.getByRole('button', { name: /add real account/i }); - expect(add_real_account_button).toBeEnabled(); + const add_real_account_button = screen.getAllByRole('button', { name: /add real account/i }); + expect(add_real_account_button[0]).toBeEnabled(); - fireEvent.click(add_real_account_button); + fireEvent.click(add_real_account_button[0]); expect(props.openDerivRealAccountNeededModal).toHaveBeenCalledTimes(1); }); @@ -272,7 +272,7 @@ describe('', () => { checkAccountCardsRendering(TESTED_CASES.NON_EU_DXTRADE); const add_real_account_buttons = screen.getAllByRole('button', { name: /add real account/i }); - expect(add_real_account_buttons.length).toBe(3); + expect(add_real_account_buttons.length).toBe(6); fireEvent.click(add_real_account_buttons[0]); expect(props.onSelectAccount).toHaveBeenCalledWith({ @@ -281,7 +281,7 @@ describe('', () => { platform: 'dxtrade', }); - fireEvent.click(add_real_account_buttons[1]); + fireEvent.click(add_real_account_buttons[2]); expect(props.onSelectAccount).toHaveBeenCalledWith({ type: 'financial', category: 'real', @@ -306,7 +306,7 @@ describe('', () => { checkAccountCardsRendering(TESTED_CASES.NON_EU_DMT5); expect(screen.queryAllByRole('button', { name: /add real account/i }).length).toBe(0); const switch_to_real_account_links = screen.getAllByText('Switch to your real account'); - expect(switch_to_real_account_links.length).toBe(3); + expect(switch_to_real_account_links.length).toBe(6); fireEvent.click(switch_to_real_account_links[0]); expect(props.toggleShouldShowRealAccountsList).toHaveBeenCalledWith(true); diff --git a/packages/cfd/src/Components/cfd-account-card.tsx b/packages/cfd/src/Components/cfd-account-card.tsx index a2dd535b06b8..08acaf7b2081 100644 --- a/packages/cfd/src/Components/cfd-account-card.tsx +++ b/packages/cfd/src/Components/cfd-account-card.tsx @@ -7,8 +7,9 @@ import { localize, Localize } from '@deriv/translations'; import { CFDAccountCopy } from './cfd-account-copy'; import { getDXTradeWebTerminalLink, - getDerivEzWebTerminalLink, getPlatformDXTradeDownloadLink, + getCTraderWebTerminalLink, + getDerivEzWebTerminalLink, } from '../Helpers/constants'; import { TAccountIconValues, @@ -212,6 +213,7 @@ const CFDAccountCardComponent = observer( const { dxtrade_tokens, derivez_tokens, + ctrader_tokens, setAccountType, setJurisdictionSelectedShortcode, setMT5TradeAccount, @@ -738,6 +740,51 @@ const CFDAccountCardComponent = observer( )} + {existing_data && + is_logged_in && + !is_web_terminal_unsupported && + platform === CFD_PLATFORMS.CTRADER && ( + + + + )} + {existing_data && is_logged_in && is_web_terminal_unsupported && ( + + + + )} + {!existing_data && is_logged_in && ( + + )} {existing_data && is_logged_in && !is_web_terminal_unsupported && diff --git a/packages/cfd/src/Components/cfd-poa.tsx b/packages/cfd/src/Components/cfd-poa.tsx index 6d8cf3f75657..27bb3793435d 100644 --- a/packages/cfd/src/Components/cfd-poa.tsx +++ b/packages/cfd/src/Components/cfd-poa.tsx @@ -1,486 +1,23 @@ import React from 'react'; -import { Field, FieldProps, Formik, FormikErrors, FormikHelpers, FormikProps } from 'formik'; -import { AccountStatusResponse, GetSettings, StatesList } from '@deriv/api-types'; -import { - AutoHeightWrapper, - DesktopWrapper, - Div100vhContainer, - Dropdown, - FormSubmitButton, - Loading, - MobileWrapper, - Modal, - SelectNative, - Text, - ThemedScrollbars, - useStateCallback, -} from '@deriv/components'; -import { FileUploaderContainer, FormSubHeader, PoaStatusCodes } from '@deriv/account'; -import { localize } from '@deriv/translations'; -import { WS, isDesktop, isMobile, validAddress, validLength, validLetterSymbol, validPostCode } from '@deriv/shared'; -import { InputField } from './cfd-personal-details-form'; -import { TJurisdiction } from '../../types'; +import { FormikValues } from 'formik/dist/types'; +import ProofOfAddressForm from '@deriv/account/src/Sections/Verification/ProofOfAddress/proof-of-address-form'; -type TErrors = { - code: string; - message: string; -}; - -type TFile = { - path: string; - lastModified: number; - lastModifiedDate: Date; - name: string; - size: number; - type: string; - webkitRelativePath: string; -}; - -type TObjDocumentFile = { - errors: TErrors[]; - file: TFile; -}; - -type TFormValuesInputs = { - address_city?: string; - address_line_1?: string; - address_line_2?: string; - address_postcode?: string; - address_state?: string; -}; - -type TDocumentFile = { - document_file?: Array; - files?: Array; -}; - -type TFormValues = TFormValuesInputs & TDocumentFile; - -type TFormValue = GetSettings; - -type TApiResponse = { - document_upload: { - call_type: number; - checksum: string; - size: number; - status: string; - upload_id: number; - }; - passthrough: { - document_upload: boolean; - }; - warning?: string; -}; - -type TStoreProofOfAddress = (file_uploader_ref: React.RefObject<(HTMLElement | null) & TUpload>) => void; - -export type TCFDPOAProps = { - onSave: (index: number, values: TFormValues) => void; +type TCFDPOA = { index: number; - onSubmit: (index: number, value: TFormValues) => void; - refreshNotifications: () => void; - form_error: string; - account_settings: GetSettings; - height: string; - states_list: StatesList; - storeProofOfAddress: TStoreProofOfAddress; - value: TFormValue; - jurisdiction_selected_shortcode: TJurisdiction; - is_authenticated_with_idv_photoid: boolean; -}; -type TUpload = { - upload: () => void; + onSave: (index: number, values: FormikValues) => void; + onSubmit: (index: number, values: FormikValues) => void; }; -let file_uploader_ref: React.RefObject; - -const CFDPOA = ({ - onSave, - index, - onSubmit, - refreshNotifications, - jurisdiction_selected_shortcode, - is_authenticated_with_idv_photoid, - ...props -}: TCFDPOAProps) => { - const form = React.useRef | null>(null); - - const [is_loading, setIsLoading] = React.useState(true); - const [form_state, setFormState] = useStateCallback({ - poa_status: 'none', - has_poi: false, - form_error: '', - }); - const [document_upload, setDocumentUpload] = useStateCallback({ files: [], error_message: null }); - const [hasPOAFailed, sethasPOAfailed] = React.useState(false); - - const validateForm = (values: TFormValuesInputs) => { - // No need to validate if we are waiting for confirmation. - if ([PoaStatusCodes.verified, PoaStatusCodes.pending].includes(form_state.poa_status)) { - return {}; - } - - const validations: Record boolean>> = { - address_line_1: [ - (v: string) => !!v && !v.match(/^\s*$/), - (v: string) => validLength(v, { max: 70 }), - (v: string) => validAddress(v).is_ok, - ], - address_line_2: [(v: string) => validLength(v, { max: 70 }), (v: string) => validAddress(v).is_ok], - address_city: [ - (v: string) => !!v && !v.match(/^\s*$/), - (v: string) => validLength(v, { min: 1, max: 35 }), - (v: string) => validLetterSymbol(v), - ], - address_state: [(v: string) => validLength(v, { max: 35 })], - address_postcode: [(v: string) => validLength(v, { max: 20 }), (v: string) => !v || validPostCode(v)], - }; - - const validation_errors: Record> = { - address_line_1: [ - localize('First line of address is required'), - localize('This should not exceed {{max}} characters.', { max: 70 }), - localize('First line of address is not in a proper format.'), - ], - address_line_2: [ - localize('This should not exceed {{max}} characters.', { max: 70 }), - localize('Second line of address is not in a proper format.'), - ], - address_city: [ - localize('Town/City is required.'), - localize('This should not exceed {{max_number}} characters.', { - max_number: 35, - }), - localize('Town/City is not in a proper format.'), - ], - address_state: [localize('State/Province is not in a proper format.')], - address_postcode: [ - localize('This should not exceed {{max_number}} characters.', { - max_number: 20, - }), - localize('Only letters, numbers, space, and hyphen are allowed.'), - ], - }; - - const errors: Record = {}; - - Object.entries(validations).forEach(([key, rules]) => { - const error_index = rules.findIndex(v => !v(values[key as keyof TFormValuesInputs] as string)); - if (error_index !== -1) { - errors[key] = validation_errors[key][error_index]; - } - }); - - return errors; - }; - - const onFileDrop = ( - files: TObjDocumentFile, - error_message: string, - setFieldTouched: (field: string, isTouched?: boolean, shouldValidate?: boolean) => void, - setFieldValue: (field: string, files_array: TObjDocumentFile) => void, - values: TFormValues - ) => { - setFieldTouched('document_file', true); - setFieldValue('document_file', files); - setDocumentUpload({ files, error_message }, () => { - // To resolve sync issues with value states (form_values in container component and formik values) - // This ensures container values are updated before being validated in runtime (mt5-financial-stp-real-account-signup.jsx) - if (typeof onSave === 'function') { - onSave(index, { ...values, ...({ document_file: files } as unknown as TDocumentFile) }); - } - }); - }; - - const onSubmitValues = async (values: TFormValues, actions: FormikHelpers) => { - const uploadables = { ...values }; - delete uploadables.document_file; - - actions.setSubmitting(true); - const data = await WS.setSettings(uploadables); - if (data.error) { - setFormState({ ...form_state, ...{ form_error: data.error.message } }); - actions.setSubmitting(false); - return; - } - const { error } = await WS.authorized.storage.getSettings(); - if (error) { - setFormState({ ...form_state, ...{ form_error: error.message } }); - return; - } - - setFormState({ ...form_state, ...{ form_error: '' } }); - - try { - const api_response = await file_uploader_ref.current?.upload(); - - if (api_response && (api_response as TApiResponse)?.warning) { - setFormState({ ...form_state, ...{ form_error: (api_response as TApiResponse).warning } }); - actions.setSubmitting(false); - return; - } - const { error: e } = await WS.authorized.storage.getAccountStatus(); - if (e) { - setFormState({ ...form_state, ...{ form_error: error.message } }); - actions.setSubmitting(false); - return; - } - } catch (e: unknown) { - setFormState({ ...form_state, ...{ form_error: (e as Error).message } }); - } - - actions.setSubmitting(false); +const CFDPOA = ({ index, onSave, onSubmit }: TCFDPOA) => { + const onSubmitForCFDModal = (index: number, values: FormikValues) => { onSave(index, values); onSubmit(index, values); }; - // didMount hook - React.useEffect(() => { - WS.authorized.getAccountStatus().then((response: AccountStatusResponse) => { - WS.wait('states_list').then(() => { - const poa_status = response.get_account_status?.authentication?.document?.status; - const poi_status = response.get_account_status?.authentication?.identity?.status; - const poa_failed_status = ['rejected', 'expired', 'suspected']; - if (poa_status && poi_status) { - const needs_poi = poi_status === 'none'; - setFormState({ ...form_state, ...{ poa_status, needs_poi, identity_status: poi_status } }, () => { - setIsLoading(false); - refreshNotifications(); - }); - } - - if (poa_status && poa_failed_status.includes(poa_status)) { - sethasPOAfailed(true); - } - }); - }); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [refreshNotifications, setFormState]); - - const isFormDisabled = (dirty: boolean, errors: FormikErrors) => { - if (form_state.poa_status === PoaStatusCodes.verified) { - return false; - } - return Object.keys(errors).length !== 0; - }; - - const { - states_list, - value: { address_line_1, address_line_2, address_city, address_state, address_postcode }, - } = props; - const { form_error, poa_status } = form_state; - - const is_form_visible = - !is_loading && (poa_status !== PoaStatusCodes.verified || is_authenticated_with_idv_photoid); - return ( - - {({ - dirty, - errors, - handleSubmit, - isSubmitting, - handleBlur, - handleChange, - setFieldTouched, - setFieldValue, - values, - touched, - }: FormikProps) => { - return ( - - {({ - setRef, - height, - }: { - setRef: (instance: HTMLFormElement | null) => void; - height: number; - }) => ( -
- - {is_loading && } - {is_form_visible && ( - -
- {hasPOAFailed && ( - - {localize( - 'We were unable to verify your address with the details you provided. Please check and resubmit or choose a different document type.' - )} - - )} - - - -
- -
- {states_list?.length > 0 ? ( - - - - {({ - field, - }: FieldProps) => ( - - )} - - - - - ) => { - handleChange(e); - setFieldValue( - 'address_state', - e.target.value, - true - ); - }} - /> - - - ) : ( - // Fallback to input field when states list is empty / unavailable for country - - )} -
- -
-
- ) => - (file_uploader_ref = ref) - } - getSocket={WS.getSocket} - onFileDrop={(df: { - files: TObjDocumentFile; - error_message: string; - }) => - onFileDrop( - df.files, - df.error_message, - setFieldTouched, - setFieldValue, - values as TFormValues - ) - } - /> -
-
-
- )} - - - {(poa_status === PoaStatusCodes.none || is_form_visible) && ( - - )} - -
-
- )} -
- ); - }} -
+
+ +
); }; diff --git a/packages/cfd/src/Components/cfd-poi.tsx b/packages/cfd/src/Components/cfd-poi.tsx index 5a021339c8a2..67ced113919b 100644 --- a/packages/cfd/src/Components/cfd-poi.tsx +++ b/packages/cfd/src/Components/cfd-poi.tsx @@ -1,6 +1,8 @@ -import { ProofOfIdentityContainerForMt5 } from '@deriv/account'; +// @ts-expect-error remove this line when ProofOfIdentityContainerForMt5 is converted to TS +import ProofOfIdentityContainerForMt5 from '@deriv/account/src/Sections/Verification/ProofOfIdentity/proof-of-identity-container-for-mt5.jsx'; import React from 'react'; import { useStore, observer } from '@deriv/stores'; +import type { TCoreStores } from '@deriv/stores/types'; type TCFDValue = { poi_state: string; @@ -10,64 +12,21 @@ type TFormValues = { poi_state?: string; }; -type TCFDNotificationByKey = { - key: string; -}; -type TCFDNotificationMessage = { - key: string; - should_show_again: string; -}; - export type TCFDPOIProps = { index: number; onSubmit: (index: number, value: TCFDValue) => void; value: TCFDValue; - addNotificationMessageByKey: (key: string) => void; + addNotificationMessageByKey: TCoreStores['notifications']['addNotificationMessageByKey']; height: string; onSave: (index: number, values: TFormValues) => void; - removeNotificationByKey: (key: TCFDNotificationByKey) => void; - removeNotificationMessage: (key: TCFDNotificationMessage) => void; + removeNotificationByKey: TCoreStores['notifications']['removeNotificationByKey']; + removeNotificationMessage: TCoreStores['notifications']['removeNotificationMessage']; jurisdiction_selected_shortcode: string; }; -const CFDPOI = observer(({ index, onSave, onSubmit, height, ...props }: TCFDPOIProps) => { - const { client, common, notifications, traders_hub } = useStore(); - - const { - account_status, - fetchResidenceList, - is_switching, - is_virtual, - is_high_risk, - is_withdrawal_lock, - should_allow_authentication, - account_settings, - residence_list, - getChangeableFields, - updateAccountStatus, - } = client; - const { routeBackInApp, app_routing_history } = common; - const { refreshNotifications } = notifications; - const { is_eu_user } = traders_hub; - - const poi_props = { - account_status, - fetchResidenceList, - is_switching, - is_virtual, - is_high_risk, - is_withdrawal_lock, - should_allow_authentication, - account_settings, - residence_list, - routeBackInApp, - app_routing_history, - refreshNotifications, - getChangeableFields, - updateAccountStatus, - is_eu_user, - ...props, - }; +const CFDPOI = observer(({ index, onSave, onSubmit, ...props }: TCFDPOIProps) => { + const { client } = useStore(); + const { account_settings, residence_list } = client; const [poi_state, setPOIState] = React.useState('none'); const citizen = account_settings?.citizen || account_settings?.country_code; @@ -80,9 +39,7 @@ const CFDPOI = observer(({ index, onSave, onSubmit, height, ...props }: TCFDPOIP }; return ( onStateChange(status)} citizen_data={citizen_data} /> diff --git a/packages/cfd/src/Components/props.types.ts b/packages/cfd/src/Components/props.types.ts index 73935daca20f..dc809c4fa404 100644 --- a/packages/cfd/src/Components/props.types.ts +++ b/packages/cfd/src/Components/props.types.ts @@ -1,7 +1,9 @@ import { DetailsOfEachMT5Loginid } from '@deriv/api-types'; import { TCFDPasswordReset } from '../Containers/props.types'; -export type TCFDPlatform = 'dxtrade' | 'mt5'; +export type TMobilePlatforms = 'ios' | 'android' | 'huawei'; + +export type TCFDPlatform = 'dxtrade' | 'mt5' | 'ctrader' | 'derivez'; export type TCFDsPlatformType = 'dxtrade' | 'derivez' | 'mt5' | 'ctrader' | ''; @@ -41,6 +43,10 @@ export type TCFDDashboardContainer = { demo: string; real: string; }; + ctrader_tokens: { + demo: string; + real: string; + }; derivez_tokens: { demo: string; real: string; @@ -88,7 +94,7 @@ export type TTradingPlatformAvailableAccount = { }; export type TModifiedTradingPlatformAvailableAccount = Omit & { - platform?: 'mt5' | 'dxtrade'; + platform?: 'mt5' | 'dxtrade' | 'ctrader'; market_type: TTradingPlatformAvailableAccount['market_type'] | 'synthetic'; }; diff --git a/packages/cfd/src/Containers/__tests__/cfd-financial-stp-real-account-signup.spec.js b/packages/cfd/src/Containers/__tests__/cfd-financial-stp-real-account-signup.spec.js index 0014b09aa3d1..a1d01eb86adf 100644 --- a/packages/cfd/src/Containers/__tests__/cfd-financial-stp-real-account-signup.spec.js +++ b/packages/cfd/src/Containers/__tests__/cfd-financial-stp-real-account-signup.spec.js @@ -25,6 +25,7 @@ jest.mock('../../Components/cfd-poa', () =>
)) ); + jest.mock('../../Components/cfd-poi', () => jest.fn(({ onCancel, onSubmit }) => (
diff --git a/packages/cfd/src/Containers/cfd-compare-accounts/__tests__/cfd-compare-accounts.spec.tsx b/packages/cfd/src/Containers/cfd-compare-accounts/__tests__/cfd-compare-accounts.spec.tsx index d5990fa6c0a6..2461d4fb9dc4 100644 --- a/packages/cfd/src/Containers/cfd-compare-accounts/__tests__/cfd-compare-accounts.spec.tsx +++ b/packages/cfd/src/Containers/cfd-compare-accounts/__tests__/cfd-compare-accounts.spec.tsx @@ -36,6 +36,14 @@ describe('', () => { icon: '"DerivX"', description: 'Example Description', }, + { + availability: 'Non-EU', + platform: 'ctrader', + name: 'cTrader', + market_type: 'all', + icon: '"CTrader"', + description: 'Example Description2', + }, ], is_demo: false, is_eu_user: false, diff --git a/packages/cfd/src/Containers/cfd-compare-accounts/cfd-compare-accounts-button.tsx b/packages/cfd/src/Containers/cfd-compare-accounts/cfd-compare-accounts-button.tsx index aa9272a9a75e..bae3a4bc36c8 100644 --- a/packages/cfd/src/Containers/cfd-compare-accounts/cfd-compare-accounts-button.tsx +++ b/packages/cfd/src/Containers/cfd-compare-accounts/cfd-compare-accounts-button.tsx @@ -11,6 +11,7 @@ import { getAccountVerficationStatus, isMt5AccountAdded, isDxtradeAccountAdded, + isCTraderAccountAdded, } from '../../Helpers/compare-accounts-config'; const CFDCompareAccountsButton = observer(({ trading_platforms, is_demo }: TCompareAccountsCard) => { @@ -66,6 +67,8 @@ const CFDCompareAccountsButton = observer(({ trading_platforms, is_demo }: TComp is_account_added = isMt5AccountAdded(current_list, market_type_shortcode, is_demo); } else if (trading_platforms.platform === CFD_PLATFORMS.DXTRADE) { is_account_added = isDxtradeAccountAdded(current_list, is_demo); + } else if (trading_platforms.platform === CFD_PLATFORMS.CTRADER) { + is_account_added = isCTraderAccountAdded(current_list, is_demo); } React.useEffect(() => { diff --git a/packages/cfd/src/Containers/cfd-compare-accounts/cfd-compare-accounts-title-icon.tsx b/packages/cfd/src/Containers/cfd-compare-accounts/cfd-compare-accounts-title-icon.tsx index 962ff68dfe74..1305ddd85fb5 100644 --- a/packages/cfd/src/Containers/cfd-compare-accounts/cfd-compare-accounts-title-icon.tsx +++ b/packages/cfd/src/Containers/cfd-compare-accounts/cfd-compare-accounts-title-icon.tsx @@ -10,11 +10,11 @@ const CFDCompareAccountsTitleIcon = ({ trading_platforms, is_eu_user, is_demo }: const market_type = !is_eu_user ? getMarketType(trading_platforms) : 'CFDs'; const market_type_shortcode = market_type.concat('_', trading_platforms.shortcode); const jurisdiction_card_icon = - trading_platforms.platform === 'dxtrade' + trading_platforms.platform === 'dxtrade' || trading_platforms.platform === 'ctrader' ? getAccountIcon(trading_platforms.platform) : getAccountIcon(market_type); const jurisdiction_card_title = - trading_platforms.platform === 'dxtrade' + trading_platforms.platform === 'dxtrade' || trading_platforms.platform === 'ctrader' ? getAccountCardTitle(trading_platforms.platform, is_demo) : getAccountCardTitle(market_type_shortcode, is_demo); const labuan_jurisdiction_message = localize( diff --git a/packages/cfd/src/Containers/cfd-compare-accounts/cfd-compare-accounts.tsx b/packages/cfd/src/Containers/cfd-compare-accounts/cfd-compare-accounts.tsx index 1fc53ca34d29..a9c4b83d9759 100644 --- a/packages/cfd/src/Containers/cfd-compare-accounts/cfd-compare-accounts.tsx +++ b/packages/cfd/src/Containers/cfd-compare-accounts/cfd-compare-accounts.tsx @@ -11,7 +11,9 @@ import { getEUAvailableAccounts, getMT5DemoData, getDxtradeDemoData, + getCtraderDemoData, dxtrade_data, + ctrader_data, } from '../../Helpers/compare-accounts-config'; const CompareCFDs = observer(() => { @@ -19,7 +21,8 @@ const CompareCFDs = observer(() => { const store = useStore(); const { client, traders_hub } = store; const { trading_platform_available_accounts } = client; - const { is_demo, is_eu_user, available_dxtrade_accounts, selected_region } = traders_hub; + const { is_demo, is_eu_user, available_dxtrade_accounts, selected_region, available_ctrader_accounts } = + traders_hub; const sorted_available_accounts = !is_eu_user ? getSortedCFDAvailableAccounts(trading_platform_available_accounts) @@ -28,6 +31,8 @@ const CompareCFDs = observer(() => { // Check if dxtrade data is available const has_dxtrade_account_available = available_dxtrade_accounts.length > 0; + const has_ctrader_account_available = available_ctrader_accounts.length > 0; + const sorted_cfd_available_eu_accounts = is_eu_user && sorted_available_accounts.length ? [...sorted_available_accounts] : []; @@ -40,6 +45,7 @@ const CompareCFDs = observer(() => { const demo_cfd_available_accounts = [ ...getMT5DemoData(all_real_sorted_cfd_available_accounts), ...getDxtradeDemoData(all_real_sorted_cfd_available_accounts), + ...getCtraderDemoData(all_real_sorted_cfd_available_accounts), ]; const all_cfd_available_accounts = @@ -48,9 +54,10 @@ const CompareCFDs = observer(() => { : all_real_sorted_cfd_available_accounts; // Calculate the card count for alignment of card in center - const card_count = has_dxtrade_account_available - ? all_cfd_available_accounts.length + 1 - : all_cfd_available_accounts.length; + const card_count = + has_dxtrade_account_available || has_ctrader_account_available + ? all_cfd_available_accounts.length + 1 + : all_cfd_available_accounts.length; const DesktopHeader = (
@@ -111,6 +118,14 @@ const CompareCFDs = observer(() => { is_demo={is_demo} /> )} + {/* Renders cTrader data */} + {all_cfd_available_accounts.length === -2 && has_ctrader_account_available && ( + + )}
@@ -152,6 +167,14 @@ const CompareCFDs = observer(() => { is_demo={is_demo} /> )} + {/* Renders cTrader data */} + {all_cfd_available_accounts.length > 0 && has_ctrader_account_available && ( + + )}
diff --git a/packages/cfd/src/Containers/cfd-dashboard.tsx b/packages/cfd/src/Containers/cfd-dashboard.tsx index da813fd7ab56..9fa21c0b212c 100644 --- a/packages/cfd/src/Containers/cfd-dashboard.tsx +++ b/packages/cfd/src/Containers/cfd-dashboard.tsx @@ -173,6 +173,7 @@ const CFDDashboard = observer((props: TCFDDashboardProps) => { createCFDAccount, current_list, dxtrade_tokens, + ctrader_tokens, derivez_tokens, } = useCfdStore(); @@ -645,6 +646,7 @@ const CFDDashboard = observer((props: TCFDDashboardProps) => { active_index={active_index} is_dark_mode_on={is_dark_mode_on} dxtrade_tokens={dxtrade_tokens} + ctrader_tokens={ctrader_tokens} derivez_tokens={derivez_tokens} /> diff --git a/packages/cfd/src/Containers/cfd-financial-stp-real-account-signup.tsx b/packages/cfd/src/Containers/cfd-financial-stp-real-account-signup.tsx index 28d3b8f723fe..607748e255b8 100644 --- a/packages/cfd/src/Containers/cfd-financial-stp-real-account-signup.tsx +++ b/packages/cfd/src/Containers/cfd-financial-stp-real-account-signup.tsx @@ -2,12 +2,12 @@ import React from 'react'; import { Div100vhContainer } from '@deriv/components'; import { useIsAccountStatusPresent } from '@deriv/hooks'; import { isDesktop, getAuthenticationStatusInfo, Jurisdiction } from '@deriv/shared'; +import { observer, useStore } from '@deriv/stores'; +import type { TCoreStores } from '@deriv/stores/types'; import CFDPOA from '../Components/cfd-poa'; import CFDPOI from '../Components/cfd-poi'; import CFDPersonalDetailsContainer from './cfd-personal-details-container'; -import { observer, useStore } from '@deriv/stores'; import { useCfdStore } from '../Stores/Modules/CFD/Helpers/useCfdStores'; -import { TCoreStores } from '@deriv/stores/types'; type TCFDFinancialStpRealAccountSignupProps = { onFinish: () => void; @@ -29,7 +29,6 @@ type TItem = { states_list: TCoreStores['client']['states_list']; fetchStatesList: TCoreStores['client']['fetchStatesList']; account_status: TCoreStores['client']['account_status']; - storeProofOfAddress: TCoreStores['modules']['cfd']['storeProofOfAddress']; jurisdiction_selected_shortcode: TCoreStores['modules']['cfd']['jurisdiction_selected_shortcode']; has_submitted_cfd_personal_details: TCoreStores['modules']['cfd']['has_submitted_cfd_personal_details']; onFinish: TCFDFinancialStpRealAccountSignupProps['onFinish']; @@ -59,7 +58,7 @@ const CFDFinancialStpRealAccountSignup = observer(({ onFinish }: TCFDFinancialSt account_status, } = client; - const { storeProofOfAddress, jurisdiction_selected_shortcode, has_submitted_cfd_personal_details } = useCfdStore(); + const { jurisdiction_selected_shortcode, has_submitted_cfd_personal_details } = useCfdStore(); const passthroughProps = { refreshNotifications, @@ -75,7 +74,6 @@ const CFDFinancialStpRealAccountSignup = observer(({ onFinish }: TCFDFinancialSt states_list, fetchStatesList, account_status, - storeProofOfAddress, jurisdiction_selected_shortcode, has_submitted_cfd_personal_details, onFinish, @@ -107,21 +105,8 @@ const CFDFinancialStpRealAccountSignup = observer(({ onFinish }: TCFDFinancialSt const poa_config: TItemsState = { body: CFDPOA, - form_value: { - address_line_1: account_settings.address_line_1, - address_line_2: account_settings.address_line_2, - address_city: account_settings.address_city, - address_state: account_settings.address_state, - address_postcode: account_settings.address_postcode, - upload_file: '', - }, - forwarded_props: [ - 'states_list', - 'account_settings', - 'storeProofOfAddress', - 'refreshNotifications', - 'jurisdiction_selected_shortcode', - ], + form_value: {}, + forwarded_props: [], }; const personal_details_config: TItemsState = { diff --git a/packages/cfd/src/Containers/cfd-password-modal.tsx b/packages/cfd/src/Containers/cfd-password-modal.tsx index 3dbf3f6c213c..7b0abd695bd0 100644 --- a/packages/cfd/src/Containers/cfd-password-modal.tsx +++ b/packages/cfd/src/Containers/cfd-password-modal.tsx @@ -1,5 +1,5 @@ -import { Formik, FormikErrors, FormikHelpers } from 'formik'; import React from 'react'; +import { Formik, FormikErrors, FormikHelpers } from 'formik'; import { useHistory } from 'react-router'; import { SentEmailModal } from '@deriv/account'; import { @@ -23,6 +23,7 @@ import { } from '@deriv/components'; import { CFD_PLATFORMS, + getCFDPlatformNames, getAuthenticationStatusInfo, getCFDPlatformLabel, getErrorMessages, @@ -187,6 +188,9 @@ const IconType = React.memo(({ platform, type, show_eu_related_content }: TIconT } else if (platform === CFD_PLATFORMS.DERIVEZ) { return ; } else if (traders_hub) { + if (platform === CFD_PLATFORMS.CTRADER) { + return ; + } switch (type) { case 'synthetic': return ; @@ -472,6 +476,17 @@ const CFDPasswordForm = ({ ); } + const accountTitle = () => { + switch (platform) { + case 'ctrader': + case 'derivez': + case 'derivx': + return 'CFD'; + default: + return account_title; + } + }; + const showJuristiction = () => { if (platform === CFD_PLATFORMS.DXTRADE) { return ''; @@ -510,12 +525,8 @@ const CFDPasswordForm = ({ i18n_default_text='Enter your {{platform}} password to add a {{platform_name}} {{account}} {{jurisdiction_shortcode}} account.' values={{ platform: getCFDPlatformLabel(platform), - // account: !show_eu_related_content ? account_title : '', - platform_name: - platform === CFD_PLATFORMS.MT5 ? 'MT5' : getCFDPlatformLabel(platform), - account: !show_eu_related_content - ? getAccountTitle(platform, account_type, account_title) - : '', + platform_name: getCFDPlatformNames(platform), + account: !show_eu_related_content ? accountTitle() : '', jurisdiction_shortcode: showJuristiction(), }} /> @@ -525,10 +536,8 @@ const CFDPasswordForm = ({ i18n_default_text='Enter your {{platform}} password to add a {{platform_name}} {{account}} account.' values={{ platform: getCFDPlatformLabel(platform), - // account: account_title, - platform_name: - platform === CFD_PLATFORMS.MT5 ? 'MT5' : getCFDPlatformLabel(platform), - account: getAccountTitle(platform, account_type, account_title), + platform_name: getCFDPlatformNames(platform), + account: accountTitle(), }} /> )} @@ -821,6 +830,15 @@ const CFDPasswordModal = observer(({ form_error, platform }: TCFDPasswordModalPr jurisdiction_selected_shortcode && getFormattedJurisdictionCode(jurisdiction_selected_shortcode); const mt5_platform_label = jurisdiction_selected_shortcode !== Jurisdiction.MALTA_INVEST ? 'Deriv MT5' : ''; + const accountTypes = () => { + if (platform === 'dxtrade' && type_label === 'Derived') { + return 'Synthetic'; + } else if (platform === 'derivez' || platform === 'ctrader') { + return 'CFDs'; + } + return type_label; + }; + if (category === 'real') { let platformName = ''; switch (platform) { @@ -840,7 +858,8 @@ const CFDPasswordModal = observer(({ form_error, platform }: TCFDPasswordModalPr , ]} diff --git a/packages/cfd/src/Containers/cfd-top-up-demo-modal.tsx b/packages/cfd/src/Containers/cfd-top-up-demo-modal.tsx index 16b750ad3d44..d58a62c58d39 100644 --- a/packages/cfd/src/Containers/cfd-top-up-demo-modal.tsx +++ b/packages/cfd/src/Containers/cfd-top-up-demo-modal.tsx @@ -3,7 +3,13 @@ import SuccessDialog from '../Components/success-dialog.jsx'; import { Icon, Modal, Button, Money, Text } from '@deriv/components'; import { getCFDPlatformLabel, CFD_PLATFORMS } from '@deriv/shared'; import { localize, Localize } from '@deriv/translations'; -import { TDxCompanies, TMtCompanies, TDerivezCompanies } from '../Stores/Modules/CFD/Helpers/cfd-config'; +import { + TDxCompanies, + TMtCompanies, + TDerivezCompanies, + TCTraderCompanies, + getCTraderCompanies, +} from '../Stores/Modules/CFD/Helpers/cfd-config'; import { getTopUpConfig } from '../Helpers/constants'; import { observer, useStore } from '@deriv/stores'; import { useCfdStore } from '../Stores/Modules/CFD/Helpers/useCfdStores'; @@ -25,6 +31,8 @@ const CFDTopUpDemoModal = observer(({ platform }: TCFDTopUpDemoModalProps) => { const { current_account, dxtrade_companies, derivez_companies, mt5_companies, topUpVirtual } = useCfdStore(); + const ctrader_companies = getCTraderCompanies(); + const getAccountTitle = React.useCallback(() => { let title = ''; if ((!mt5_companies && !dxtrade_companies && !derivez_companies) || !current_account) return ''; @@ -42,6 +50,12 @@ const CFDTopUpDemoModal = observer(({ platform }: TCFDTopUpDemoModalProps) => { current_account.type as keyof TDerivezCompanies['demo' | 'real'] ].title; break; + case CFD_PLATFORMS.CTRADER: + title = + ctrader_companies[current_account.category as keyof TCTraderCompanies][ + current_account.type as keyof TCTraderCompanies['demo' | 'real'] + ].title; + break; case CFD_PLATFORMS.DXTRADE: title = dxtrade_companies[current_account.category as keyof TDxCompanies][ @@ -59,9 +73,12 @@ const CFDTopUpDemoModal = observer(({ platform }: TCFDTopUpDemoModalProps) => { closeSuccessTopUpModal(); }; - const platform_title = getCFDPlatformLabel(platform); + const has_sub_title = [CFD_PLATFORMS.CTRADER].includes(platform); + + const platform_title = getCFDPlatformLabel(platform, has_sub_title); - if ((!mt5_companies && !dxtrade_companies && !derivez_companies) || !current_account) return null; + if ((!mt5_companies && !dxtrade_companies && !derivez_companies && !getCTraderCompanies()) || !current_account) + return null; const { minimum_amount, additional_amount } = getTopUpConfig(); return ( diff --git a/packages/cfd/src/Containers/jurisdiction-modal/__test__/jurisdiction-modal-content.spec.tsx b/packages/cfd/src/Containers/jurisdiction-modal/__test__/jurisdiction-modal-content.spec.tsx index 3fdc99c741c6..83a27b2ec474 100644 --- a/packages/cfd/src/Containers/jurisdiction-modal/__test__/jurisdiction-modal-content.spec.tsx +++ b/packages/cfd/src/Containers/jurisdiction-modal/__test__/jurisdiction-modal-content.spec.tsx @@ -19,6 +19,24 @@ describe('JurisdictionModalContent', () => { toggleDynamicLeverage: jest.fn(), jurisdiction_selected_shortcode: '', setJurisdictionSelectedShortcode: jest.fn(), + all_market_type_available_accounts: [ + { + market_type: 'all' as const, + name: '', + requirements: { + after_first_deposit: { + financial_assessment: [''], + }, + compliance: { + mt5: [''], + tax_information: [''], + }, + signup: [''], + }, + shortcode: Jurisdiction.SVG, + sub_account_type: '', + }, + ], synthetic_available_accounts: [ { market_type: 'gaming' as const, @@ -247,6 +265,7 @@ describe('JurisdictionModalContent', () => { { ...mock_props.financial_available_accounts[0], shortcode: Jurisdiction.MALTA_INVEST }, ]; mock_props.synthetic_available_accounts = []; + mock_props.all_market_type_available_accounts = []; render(); const container = screen.getByTestId('dt-jurisdiction-modal-content'); expect(container).toHaveClass('cfd-jurisdiction-card--financial__wrapper'); diff --git a/packages/cfd/src/Containers/jurisdiction-modal/__test__/jurisdiction-modal-foot-note.spec.tsx b/packages/cfd/src/Containers/jurisdiction-modal/__test__/jurisdiction-modal-foot-note.spec.tsx index ff4227723950..93dd644eead7 100644 --- a/packages/cfd/src/Containers/jurisdiction-modal/__test__/jurisdiction-modal-foot-note.spec.tsx +++ b/packages/cfd/src/Containers/jurisdiction-modal/__test__/jurisdiction-modal-foot-note.spec.tsx @@ -3,13 +3,19 @@ import JurisdictionModalFootNote from '../jurisdiction-modal-foot-note'; import { render, screen } from '@testing-library/react'; import RootStore from 'Stores/index'; import { Jurisdiction } from '@deriv/shared'; +import { StoreProvider, mockStore } from '@deriv/stores'; describe('JurisdictionModalFootNote', () => { - const mock_store = { + const mock_store = mockStore({ common: {}, client: {}, ui: {}, - }; + }); + + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); + const mock_context = new RootStore(mock_store); const mock_props = { account_status: { @@ -33,12 +39,12 @@ describe('JurisdictionModalFootNote', () => { should_restrict_vanuatu_account_creation: false, }; it('should render JurisdictionModalFootNote', () => { - render(); + render(, { wrapper }); expect(screen.getByTestId('dt-jurisdiction-footnote')).toBeInTheDocument(); }); it('should render JurisdictionModalFootNote with className', () => { - render(); + render(, { wrapper }); const container = screen.getByTestId('dt-jurisdiction-footnote'); expect(container).toHaveClass('mock_jurisdiction__footnote'); }); @@ -49,7 +55,8 @@ describe('JurisdictionModalFootNote', () => { {...mock_props} jurisdiction_selected_shortcode={Jurisdiction.SVG} account_type='synthetic' - /> + />, + { wrapper } ); expect( screen.getByText('Add your Deriv MT5 Derived account under Deriv (SVG) LLC (company no. 273 LLC 2020).') @@ -62,7 +69,8 @@ describe('JurisdictionModalFootNote', () => { {...mock_props} jurisdiction_selected_shortcode={Jurisdiction.BVI} account_type='synthetic' - /> + />, + { wrapper } ); expect( screen.getByText( @@ -78,7 +86,8 @@ describe('JurisdictionModalFootNote', () => { jurisdiction_selected_shortcode={Jurisdiction.BVI} account_type='synthetic' should_restrict_bvi_account_creation - /> + />, + { wrapper } ); expect( screen.getByText('To create this account first we need you to resubmit your proof of address.') @@ -107,7 +116,8 @@ describe('JurisdictionModalFootNote', () => { account_type='synthetic' should_restrict_bvi_account_creation card_classname='mock_jurisdiction' - /> + />, + { wrapper } ); const poa_message = screen.getByText( 'You can open this account once your submitted documents have been verified.' @@ -122,7 +132,8 @@ describe('JurisdictionModalFootNote', () => { {...mock_props} jurisdiction_selected_shortcode={Jurisdiction.VANUATU} account_type='synthetic' - /> + />, + { wrapper } ); expect( screen.getByText( @@ -138,7 +149,8 @@ describe('JurisdictionModalFootNote', () => { jurisdiction_selected_shortcode={Jurisdiction.VANUATU} account_type='synthetic' should_restrict_vanuatu_account_creation - /> + />, + { wrapper } ); expect( screen.getByText('To create this account first we need you to resubmit your proof of address.') @@ -167,7 +179,8 @@ describe('JurisdictionModalFootNote', () => { account_type='synthetic' should_restrict_vanuatu_account_creation card_classname='mock_jurisdiction' - /> + />, + { wrapper } ); const poa_message = screen.getByText( 'You can open this account once your submitted documents have been verified.' @@ -182,7 +195,8 @@ describe('JurisdictionModalFootNote', () => { {...mock_props} jurisdiction_selected_shortcode={Jurisdiction.LABUAN} account_type='synthetic' - /> + />, + { wrapper } ); expect( screen.getByText( @@ -197,7 +211,8 @@ describe('JurisdictionModalFootNote', () => { {...mock_props} jurisdiction_selected_shortcode={Jurisdiction.MALTA_INVEST} account_type='synthetic' - /> + />, + { wrapper } ); expect( screen.getByText( @@ -207,7 +222,7 @@ describe('JurisdictionModalFootNote', () => { }); it('should not render JurisdictionModalFootNote when jurisdiction_shortcode is empty', () => { - render(); + render(, { wrapper }); expect(screen.queryByTestId('dt-jurisdiction-footnote')).not.toBeInTheDocument(); }); }); diff --git a/packages/cfd/src/Containers/jurisdiction-modal/__test__/jurisdiction-modal-title.spec.tsx b/packages/cfd/src/Containers/jurisdiction-modal/__test__/jurisdiction-modal-title.spec.tsx index 08cd56c7f119..2ee7105b2345 100644 --- a/packages/cfd/src/Containers/jurisdiction-modal/__test__/jurisdiction-modal-title.spec.tsx +++ b/packages/cfd/src/Containers/jurisdiction-modal/__test__/jurisdiction-modal-title.spec.tsx @@ -9,6 +9,7 @@ type TMockProps = { toggleDynamicLeverage: React.MouseEventHandler; account_type: string; show_eu_related_content: boolean; + platform: any; }; const mock_props = { @@ -16,6 +17,7 @@ const mock_props = { toggleDynamicLeverage: jest.fn(), account_type: 'Financial', show_eu_related_content: false, + platform: 'mt5', }; const JurisdictionModalTitleComponent = ({ @@ -36,22 +38,31 @@ const JurisdictionModalTitleComponent = ({ describe('JurisdictionModalTitle', () => { it('should render JurisdictionModalTitle', () => { - render(); + const new_props = { ...mock_props, platform: 'mt5' }; + render(); const title = screen.getByText(/jurisdiction/); expect(title).toBeInTheDocument(); expect(title).toHaveTextContent('Choose a jurisdiction for your Deriv MT5 Financial account'); }); it('should render JurisdictionModalTitle correctly if show_eu_related_content is true', () => { - const new_props = { ...mock_props, show_eu_related_content: true }; + const new_props = { ...mock_props, show_eu_related_content: true, platform: 'mt5' }; render(); const title = screen.getByText(/jurisdiction/); expect(title).toBeInTheDocument(); expect(title).toHaveTextContent('Choose a jurisdiction for your Deriv MT5 CFDs account'); }); + it('should render JurisdictionModalTitle correctly if show_eu_related_content is true', () => { + const new_props = { ...mock_props, platform: 'ctrader' }; + render(); + const title = screen.getByText(/jurisdiction/); + expect(title).toBeInTheDocument(); + expect(title).toHaveTextContent('Choose a jurisdiction for your cTrader account'); + }); + it('should render JurisdictionModalTitle correctly if is_dynamic_leverage_visible is true', () => { - const new_props = { ...mock_props, is_dynamic_leverage_visible: true }; + const new_props = { ...mock_props, is_dynamic_leverage_visible: true, platform: 'mt5' }; render(); const title = screen.getByText(/Deriv/); expect(title).toBeInTheDocument(); diff --git a/packages/cfd/src/Containers/jurisdiction-modal/__test__/jurisdiction-modal.spec.tsx b/packages/cfd/src/Containers/jurisdiction-modal/__test__/jurisdiction-modal.spec.tsx index 69c390c808e1..7e40debfc423 100644 --- a/packages/cfd/src/Containers/jurisdiction-modal/__test__/jurisdiction-modal.spec.tsx +++ b/packages/cfd/src/Containers/jurisdiction-modal/__test__/jurisdiction-modal.spec.tsx @@ -81,6 +81,10 @@ describe('JurisdictionModal', () => { }; it('should render JurisdictionModal', () => { + store = mockStore({ + ...mock_store, + common: { platform: 'mt5' }, + }); render(); const title = screen.getByRole('heading'); @@ -91,27 +95,12 @@ describe('JurisdictionModal', () => { expect(close_button).toBeInTheDocument(); }); - it('should render JurisdictionModal with dynamic leverage modal', async () => { - render(); - const toggle_button = screen.getByText('Dynamic Leverage'); - userEvent.click(toggle_button); - - const title = screen.getByRole('heading'); - const back_button = screen.getByTestId('back_icon'); - const modal_content = screen.getByTestId('modal_content'); - - expect(modal_content).toBeInTheDocument(); - expect(modal_content).toHaveClass('jurisdiction-modal__flipped'); - expect(title).toBeInTheDocument(); - expect(title).toHaveTextContent('Get more out of Deriv MT5 Financial'); - expect(back_button).toBeInTheDocument(); - - userEvent.click(back_button); - expect(modal_content).not.toHaveClass('jurisdiction-modal__flipped'); - }); - it('should render JurisdictionModal with show_eu_related_content', () => { - store = mockStore({ ...mock_store, traders_hub: { show_eu_related_content: true } }); + store = mockStore({ + ...mock_store, + traders_hub: { show_eu_related_content: true }, + common: { platform: 'mt5' }, + }); render(); diff --git a/packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-modal-content-wrapper.tsx b/packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-modal-content-wrapper.tsx index aecf6eea4673..1b96559034f6 100644 --- a/packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-modal-content-wrapper.tsx +++ b/packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-modal-content-wrapper.tsx @@ -84,6 +84,10 @@ const JurisdictionModalContentWrapper = observer(({ openPasswordModal }: TJurisd : available_account.shortcode !== 'maltainvest') ); + const all_market_type_available_accounts = trading_platform_available_accounts?.filter( + available_account => available_account.market_type === 'all' + ); + const is_svg_selected = jurisdiction_selected_shortcode === Jurisdiction.SVG; const is_bvi_selected = jurisdiction_selected_shortcode === Jurisdiction.BVI; const is_vanuatu_selected = jurisdiction_selected_shortcode === Jurisdiction.VANUATU; @@ -202,6 +206,7 @@ const JurisdictionModalContentWrapper = observer(({ openPasswordModal }: TJurisd setJurisdictionSelectedShortcode={setJurisdictionSelectedShortcode} swapfree_available_accounts={swapfree_available_accounts} synthetic_available_accounts={synthetic_available_accounts} + all_market_type_available_accounts={all_market_type_available_accounts} />
{ + const { common } = useStore(); + + const { platform } = common; + const account_type_name = getMT5Title(account_type); const { poa_pending } = getAuthenticationStatusInfo(account_status); + if (platform === CFD_PLATFORMS.CTRADER && jurisdiction_selected_shortcode === 'svg') { + return ( + + ); + } if (jurisdiction_selected_shortcode === Jurisdiction.SVG) { return ( { ); }; -export default JurisdictionModalFootNote; +export default observer(JurisdictionModalFootNote); diff --git a/packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-modal-title.tsx b/packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-modal-title.tsx index 0080916430ac..8baba6506c3e 100644 --- a/packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-modal-title.tsx +++ b/packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-modal-title.tsx @@ -1,11 +1,15 @@ import React from 'react'; import { Icon, Text } from '@deriv/components'; -import { getMT5Title, isMobile } from '@deriv/shared'; +import { getMT5Title, isMobile, CFD_PLATFORMS } from '@deriv/shared'; import { Localize, localize } from '@deriv/translations'; import { useDynamicLeverage } from '../dynamic-leverage/dynamic-leverage-context'; import { TJurisdictionModalTitleProps } from '../props.types'; -export const JurisdictionModalTitle = ({ show_eu_related_content, account_type }: TJurisdictionModalTitleProps) => { +export const JurisdictionModalTitle = ({ + show_eu_related_content, + account_type, + platform, +}: TJurisdictionModalTitleProps) => { const { is_dynamic_leverage_visible, toggleDynamicLeverage } = useDynamicLeverage(); if (is_dynamic_leverage_visible) { return ( @@ -28,7 +32,11 @@ export const JurisdictionModalTitle = ({ show_eu_related_content, account_type } return ( ); diff --git a/packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-modal.tsx b/packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-modal.tsx index fd494741bb38..71a8f53fd58a 100644 --- a/packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-modal.tsx +++ b/packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-modal.tsx @@ -10,10 +10,11 @@ import JurisdictionModalContentWrapper from './jurisdiction-modal-content-wrappe import JurisdictionModalTitle from './jurisdiction-modal-title'; const JurisdictionModal = observer(({ openPasswordModal }: TJurisdictionModalProps) => { - const { traders_hub, ui } = useStore(); + const { traders_hub, ui, common } = useStore(); const { show_eu_related_content } = traders_hub; const { disableApp, enableApp } = ui; + const { platform } = common; const { account_type, is_jurisdiction_modal_visible, toggleJurisdictionModal } = useCfdStore(); @@ -60,6 +61,7 @@ const JurisdictionModal = observer(({ openPasswordModal }: TJurisdictionModalPro } > @@ -76,6 +78,7 @@ const JurisdictionModal = observer(({ openPasswordModal }: TJurisdictionModalPro } > diff --git a/packages/cfd/src/Containers/mt5-trade-modal.tsx b/packages/cfd/src/Containers/mt5-trade-modal.tsx index ca89966bdf4b..57f112c0eb91 100644 --- a/packages/cfd/src/Containers/mt5-trade-modal.tsx +++ b/packages/cfd/src/Containers/mt5-trade-modal.tsx @@ -28,7 +28,7 @@ const MT5TradeModal = observer( const { show_eu_related_content } = traders_hub; const { platform } = common; - const { mt5_trade_account, dxtrade_tokens, derivez_tokens } = useCfdStore(); + const { mt5_trade_account, dxtrade_tokens, derivez_tokens, ctrader_tokens } = useCfdStore(); const CFDTradeModal = () => { if (platform === 'mt5') { @@ -49,6 +49,7 @@ const MT5TradeModal = observer( toggleModal={toggleModal} is_demo={is_demo} platform={platform} + ctrader_tokens={ctrader_tokens} dxtrade_tokens={dxtrade_tokens} derivez_tokens={derivez_tokens} /> diff --git a/packages/cfd/src/Containers/props.types.ts b/packages/cfd/src/Containers/props.types.ts index 648c54ddbaac..7361e2f95061 100644 --- a/packages/cfd/src/Containers/props.types.ts +++ b/packages/cfd/src/Containers/props.types.ts @@ -16,6 +16,7 @@ import { TClickableDescription, TJurisdictionCardItems, TJurisdictionCardItemVerification, + TCFDsPlatformType, } from '../Components/props.types'; import RootStore from '../Stores/index'; @@ -23,7 +24,7 @@ export type TCFDPersonalDetailsContainerProps = { onSubmit: (index: number, value: { [key: string]: string }) => void; }; -type CFD_Platform = 'dxtrade' | 'mt5'; +type CFD_Platform = 'dxtrade' | 'mt5' | 'derivez' | 'ctrader'; export type TCFDChangePasswordConfirmationProps = { confirm_label?: string; @@ -42,6 +43,10 @@ export type TCFDDashboardContainer = { demo: string; real: string; }; + ctrader_tokens: { + demo: string; + real: string; + }; derivez_tokens: { demo: string; real: string; @@ -243,6 +248,7 @@ export type TJurisdictionModalContentProps = { setJurisdictionSelectedShortcode: (card_type: string) => void; synthetic_available_accounts: TTradingPlatformAvailableAccount[]; financial_available_accounts: TTradingPlatformAvailableAccount[]; + all_market_type_available_accounts: TTradingPlatformAvailableAccount[]; swapfree_available_accounts: TTradingPlatformAvailableAccount[]; real_synthetic_accounts_existing_data: TExistingData; real_financial_accounts_existing_data: TExistingData; @@ -253,6 +259,7 @@ export type TJurisdictionModalContentProps = { export type TJurisdictionModalTitleProps = { show_eu_related_content: boolean; account_type: string; + platform: TCFDsPlatformType; }; type TAccountStatus = Omit & Partial>; diff --git a/packages/cfd/src/Containers/trade-modal.tsx b/packages/cfd/src/Containers/trade-modal.tsx index e24a287390a4..c15c5e65b269 100644 --- a/packages/cfd/src/Containers/trade-modal.tsx +++ b/packages/cfd/src/Containers/trade-modal.tsx @@ -4,8 +4,8 @@ import { TTradingPlatformAccounts, TCFDDashboardContainer, TCFDsPlatformType } f import { DetailsOfEachMT5Loginid } from '@deriv/api-types'; import { CFD_PLATFORMS, getCFDAccountKey, isMobile } from '@deriv/shared'; import { localize, Localize } from '@deriv/translations'; -import { getPlatformQRCode, PlatformsDesktopDownload } from '../Helpers/config'; -import { getTitle, platformsText, mobileDownloadLink } from '../Helpers/constants'; +import { getPlatformQRCode, PlatformsDesktopDownload, mobileDownloadLink } from '../Helpers/config'; +import { getTitle, platformsText, CTRADER_DESKTOP_DOWNLOAD } from '../Helpers/constants'; import SpecBox from '../Components/specbox'; import PasswordBox from '../Components/passwordbox'; import { TCFDPasswordReset } from './props.types'; @@ -22,6 +22,7 @@ type TTradeModalProps = { ) => void; toggleModal: () => void; dxtrade_tokens: TCFDDashboardContainer['dxtrade_tokens']; + ctrader_tokens: TCFDDashboardContainer['ctrader_tokens']; derivez_tokens: TCFDDashboardContainer['derivez_tokens']; is_demo: string; platform: TCFDsPlatformType; @@ -38,18 +39,26 @@ const PlatformIconsAndDescriptions = (
, + ]} /> - {(mt5_trade_account as TTradingPlatformAccounts)?.display_login && ( - - {(mt5_trade_account as TTradingPlatformAccounts)?.display_login} - - )} + {platform !== CFD_PLATFORMS.CTRADER + ? (mt5_trade_account as TTradingPlatformAccounts)?.display_login && ( + + {(mt5_trade_account as TTradingPlatformAccounts)?.display_login} + + ) + : ''}
); @@ -62,6 +71,7 @@ const TradeModal = ({ toggleModal, dxtrade_tokens, derivez_tokens, + ctrader_tokens, is_demo, platform, }: TTradeModalProps) => { @@ -113,7 +123,7 @@ const TradeModal = ({ size={isMobile() ? 'xxxs' : 'xxs'} weight='bold' > - {localize('Download Deriv cTrader on your phone to trade with the Deriv cTrader account')} + {localize('Download cTrader on your phone to trade with the Deriv cTrader account')} ); default: @@ -128,7 +138,7 @@ const TradeModal = ({ } else if (platform_type === 'derivez') { app_title = localize('Run Deriv EZ on your browser'); } else if (platform_type === 'ctrader') { - app_title = localize('Run Deriv cTrader on your browser'); + app_title = localize('Run cTrader on your browser'); } else { return null; } @@ -144,6 +154,7 @@ const TradeModal = ({ is_demo={is_demo} dxtrade_tokens={dxtrade_tokens} derivez_tokens={derivez_tokens} + ctrader_tokens={ctrader_tokens} />
@@ -155,7 +166,12 @@ const TradeModal = ({
{PlatformIconsAndDescriptions(platform, is_demo, mt5_trade_account)} {mt5_trade_account?.display_balance && ( - +
@@ -217,32 +233,65 @@ const TradeModal = ({ {platform === CFD_PLATFORMS.DERIVEZ && ( )} + {platform === CFD_PLATFORMS.CTRADER && ( + + )}
{downloadCenterAppOption(platform)}
-
{downloadCenterDescription()}
-
-
-
- - - -
- - - - - + {platform === CFD_PLATFORMS.CTRADER && ( + - {!isMobile() && ( -
- {getPlatformQRCode(platform)} + )} + {platform !== CFD_PLATFORMS.CTRADER && ( + +
{downloadCenterDescription()}
+ +
+
+
+ + + +
+ + + + {platform !== CFD_PLATFORMS.CTRADER && ( + + + + )} +
+ {!isMobile() && ( +
+ {getPlatformQRCode(platform)} +
+ )}
- )} -
+ + )}
); }; diff --git a/packages/cfd/src/Helpers/compare-accounts-config.ts b/packages/cfd/src/Helpers/compare-accounts-config.ts index f20367dedc97..df97d87cca9b 100644 --- a/packages/cfd/src/Helpers/compare-accounts-config.ts +++ b/packages/cfd/src/Helpers/compare-accounts-config.ts @@ -19,6 +19,7 @@ const getHighlightedIconLabel = ( ['financial_labuan', 'financial_vanuatu'].includes(market_type_shortcode) || is_demo || trading_platforms.platform === CFD_PLATFORMS.DXTRADE || + trading_platforms.platform === CFD_PLATFORMS.CTRADER || selected_region === 'EU' || (trading_platforms.platform === CFD_PLATFORMS.MT5 && market_type_shortcode === 'all_svg') ? localize('Forex') @@ -123,6 +124,8 @@ const getAccountCardTitle = (shortcode: string, is_demo?: boolean) => { return is_demo ? localize('Swap-Free Demo') : localize('Swap-Free - SVG'); case 'dxtrade': return is_demo ? localize('Deriv X Demo') : localize('Deriv X'); + case 'ctrader': + return is_demo ? localize('cTrader Demo') : localize('cTrader'); default: return is_demo ? localize('CFDs Demo') : localize('CFDs'); } @@ -132,6 +135,7 @@ const getAccountCardTitle = (shortcode: string, is_demo?: boolean) => { const getPlatformLabel = (shortcode?: string) => { switch (shortcode) { case 'dxtrade': + case 'ctrader': case 'CFDs': return localize('Other CFDs Platform'); case 'mt5': @@ -157,6 +161,8 @@ const getAccountIcon = (shortcode: string) => { return 'SwapFree'; case 'dxtrade': return 'DerivX'; + case 'ctrader': + return 'CTrader'; default: return 'CFDs'; } @@ -314,6 +320,24 @@ const dxtrade_data: TModifiedTradingPlatformAvailableAccount = { platform: 'dxtrade', }; +const ctrader_data: TModifiedTradingPlatformAvailableAccount = { + market_type: 'all', + name: 'cTrader', + requirements: { + after_first_deposit: { + financial_assessment: [''], + }, + compliance: { + mt5: [''], + tax_information: [''], + }, + signup: [''], + }, + shortcode: 'svg', + sub_account_type: '', + platform: 'ctrader', +}; + // Check whether the POA POI status are completed for different jurisdictions const getAccountVerficationStatus = ( market_type_shortcode: string, @@ -392,6 +416,12 @@ const isDxtradeAccountAdded = (current_list: Record, is_demo?: boolean) => + Object.entries(current_list).some(([key, value]) => { + const current_account_type = is_demo ? 'demo' : 'real'; + return value.account_type === current_account_type && key.includes(CFD_PLATFORMS.CTRADER); + }); + // Get the MT5 demo accounts of the user const getMT5DemoData = (available_accounts: TModifiedTradingPlatformAvailableAccount[]) => { const swap_free_demo_accounts = available_accounts.filter( @@ -409,6 +439,10 @@ const getDxtradeDemoData = (available_accounts: TModifiedTradingPlatformAvailabl return available_accounts.filter(item => item.platform === CFD_PLATFORMS.DXTRADE); }; +const getCtraderDemoData = (available_accounts: TModifiedTradingPlatformAvailableAccount[]) => { + return available_accounts.filter(item => item.platform === CFD_PLATFORMS.CTRADER); +}; + export { getHighlightedIconLabel, getJuridisctionDescription, @@ -419,11 +453,14 @@ export { getSortedCFDAvailableAccounts, getEUAvailableAccounts, dxtrade_data, + ctrader_data, getHeaderColor, platfromsHeaderLabel, getAccountVerficationStatus, isMt5AccountAdded, isDxtradeAccountAdded, + isCTraderAccountAdded, getMT5DemoData, getDxtradeDemoData, + getCtraderDemoData, }; diff --git a/packages/cfd/src/Helpers/config.tsx b/packages/cfd/src/Helpers/config.tsx index f955b624f06f..b3a0454ea90f 100644 --- a/packages/cfd/src/Helpers/config.tsx +++ b/packages/cfd/src/Helpers/config.tsx @@ -2,33 +2,60 @@ import React from 'react'; import { QRCode } from 'react-qrcode'; import { TCFDsPlatformType } from 'Components/props.types'; import { + getPlatformDXTradeDownloadLink, + getPlatformCTraderDownloadLink, + getPlatformDerivEZDownloadLink, getDXTradeWebTerminalLink, getDerivEzWebTerminalLink, + getCTraderWebTerminalLink, platformsText, platformsIcons, - mobileDownloadLink, } from './constants'; +import { isMobile, CFD_PLATFORMS } from '@deriv/shared'; import { Text, Icon } from '@deriv/components'; import { Localize } from '@deriv/translations'; -import { isMobile, OSDetect, isDesktopOs } from '@deriv/shared'; import { TCFDDashboardContainer } from 'Containers/props.types'; +export const mobileDownloadLink = (platform: TCFDsPlatformType, type: 'ios' | 'android' | 'huawei') => { + switch (platform) { + case 'dxtrade': + return getPlatformDXTradeDownloadLink(type); + case 'ctrader': + return getPlatformCTraderDownloadLink(type); + case 'derivez': + return getPlatformDerivEZDownloadLink(type); + default: + return getPlatformDXTradeDownloadLink(type); + } +}; + export const getPlatformQRCode = (acc_type: TCFDsPlatformType) => { - const qr_code_width = isMobile() ? '100%' : '80%'; - const os = OSDetect(); - const checkForDesktop = isDesktopOs() ? (os === 'mac' ? 'ios' : 'android') : os; + const qr_code_mobile = isMobile() ? '100%' : '80%'; + + const QRCodeLinks = () => { + switch (acc_type) { + case 'derivez': + return 'https://onelink.to/bkdwkd'; + case 'dxtrade': + return 'https://onelink.to/grmtyx'; + case 'ctrader': + return 'https://onelink.to/yvk2a5'; + default: + return 'https://onelink.to/grmtyx'; + } + }; return ( @@ -38,6 +65,7 @@ export const getPlatformQRCode = (acc_type: TCFDsPlatformType) => { type TPlatformsDesktopDownload = { platform: TCFDsPlatformType; dxtrade_tokens: TCFDDashboardContainer['dxtrade_tokens']; + ctrader_tokens: TCFDDashboardContainer['ctrader_tokens']; derivez_tokens: TCFDDashboardContainer['derivez_tokens']; is_demo: string; }; @@ -45,11 +73,18 @@ type TPlatformsDesktopDownload = { export const PlatformsDesktopDownload = ({ platform, dxtrade_tokens, + ctrader_tokens, derivez_tokens, is_demo, }: TPlatformsDesktopDownload) => { const PlatformsDesktopDownloadLinks = () => { switch (platform) { + case 'ctrader': + return getCTraderWebTerminalLink( + is_demo ? 'demo' : 'real', + ctrader_tokens && ctrader_tokens[is_demo ? 'demo' : 'real'] + ); + case 'derivez': return getDerivEzWebTerminalLink( is_demo ? 'demo' : 'real', @@ -68,17 +103,26 @@ export const PlatformsDesktopDownload = ({ return ( - -
+ {platform === CFD_PLATFORMS.CTRADER ? ( + + ) : ( + + )} +
diff --git a/packages/cfd/src/Helpers/constants.ts b/packages/cfd/src/Helpers/constants.ts index c272393ed11b..11f9de32ab51 100644 --- a/packages/cfd/src/Helpers/constants.ts +++ b/packages/cfd/src/Helpers/constants.ts @@ -1,9 +1,11 @@ import { OSDetect } from '@deriv/shared'; import { localize } from '@deriv/translations'; -import { TCFDsPlatformType } from 'Components/props.types'; +import { TCFDsPlatformType, TMobilePlatforms } from 'Components/props.types'; const platformsText = (platform: TCFDsPlatformType) => { switch (platform) { + case 'ctrader': + return 'cTrader'; case 'derivez': return 'EZ'; case 'dxtrade': @@ -19,17 +21,8 @@ const platformsIcons = (platform: TCFDsPlatformType) => { return 'DerivEz'; case 'dxtrade': return 'Dxtrade'; - default: - return ''; - } -}; - -const mobileDownloadLink = (platform: TCFDsPlatformType, type: 'ios' | 'android' | 'huawei') => { - switch (platform) { - case 'dxtrade': - return getPlatformDXTradeDownloadLink(type); - case 'derivez': - return getPlatformDerivEZDownloadLink(type); + case 'ctrader': + return 'Ctrader'; default: return ''; } @@ -43,6 +36,12 @@ const getTitle = (market_type: string, is_eu_user: boolean) => { const REAL_DXTRADE_URL = 'https://dx.deriv.com'; const DEMO_DXTRADE_URL = 'https://dx-demo.deriv.com'; +const CTRADER_DESKTOP_DOWNLOAD = 'https://deriv.ctrader.com/cbrokerdemo-deriv-setup.exe'; + +const CTRADER_DOWNLOAD_LINK = 'https://ctrader.com/download/'; + +const CTRADER_URL = 'https://ct.deriv.com/'; + const DERIVEZ_URL = 'https://dqwsqxuu0r6t9.cloudfront.net/'; const DERIVEZ_IOS_APP_URL = 'https://apps.apple.com/my/app/deriv-go/id1550561298'; const DERIVEZ_ANDROID_APP_URL = 'https://play.google.com/store/apps/details?id=com.deriv.app&pli=1'; @@ -52,6 +51,9 @@ const DXTRADE_IOS_APP_URL = 'https://apps.apple.com/us/app/deriv-x/id1563337503' const DXTRADE_ANDROID_APP_URL = 'https://play.google.com/store/apps/details?id=com.deriv.dx'; const DXTRADE_HUAWEI_APP_URL = 'https://appgallery.huawei.com/app/C104633219'; +const CTRADER_IOS_APP_URL = 'https://apps.apple.com/cy/app/ctrader/id767428811'; +const CTRADER_ANDROID_APP_URL = 'https://play.google.com/store/apps/details?id=com.spotware.ct&hl=en&gl=US'; + const getBrokerName = () => 'Deriv Holdings (Guernsey) Limited'; const getTopUpConfig = () => { @@ -61,7 +63,7 @@ const getTopUpConfig = () => { }; }; -const getPlatformDXTradeDownloadLink = (platform?: 'ios' | 'android' | 'huawei') => { +const getPlatformDXTradeDownloadLink = (platform?: TMobilePlatforms) => { switch (platform) { case 'ios': return DXTRADE_IOS_APP_URL; @@ -87,6 +89,19 @@ const getPlatformDerivEZDownloadLink = (platform: 'ios' | 'android' | 'huawei') } }; +const getPlatformCTraderDownloadLink = (platform: TMobilePlatforms) => { + switch (platform) { + case 'ios': + return CTRADER_IOS_APP_URL; + case 'android': + return CTRADER_ANDROID_APP_URL; + case 'huawei': + return ''; + default: + return CTRADER_ANDROID_APP_URL; + } +}; + const getPlatformMt5DownloadLink = (platform: string | undefined = undefined) => { switch (platform || OSDetect()) { case 'windows': @@ -116,6 +131,10 @@ const getDXTradeWebTerminalLink = (category: string, token?: string) => { return url; }; +const getCTraderWebTerminalLink = (category: string, token?: string) => { + return `${CTRADER_URL}${token && `?token=${token}`}`; +}; + const getDerivEzWebTerminalLink = (category: string, token?: string) => { let url = DERIVEZ_URL; @@ -145,16 +164,20 @@ const getMT5WebTerminalLink = ({ export { REAL_DXTRADE_URL, DEMO_DXTRADE_URL, + CTRADER_URL, DERIVEZ_URL, + CTRADER_DOWNLOAD_LINK, getBrokerName, platformsText, - platformsIcons, - getTitle, - mobileDownloadLink, getPlatformDXTradeDownloadLink, + getPlatformCTraderDownloadLink, getPlatformDerivEZDownloadLink, getPlatformMt5DownloadLink, + CTRADER_DESKTOP_DOWNLOAD, getDXTradeWebTerminalLink, + getCTraderWebTerminalLink, + platformsIcons, + getTitle, getDerivEzWebTerminalLink, getMT5WebTerminalLink, getTopUpConfig, diff --git a/packages/cfd/src/Stores/Modules/CFD/Helpers/cfd-config.ts b/packages/cfd/src/Stores/Modules/CFD/Helpers/cfd-config.ts index f92076ce6771..5429aad3c3be 100644 --- a/packages/cfd/src/Stores/Modules/CFD/Helpers/cfd-config.ts +++ b/packages/cfd/src/Stores/Modules/CFD/Helpers/cfd-config.ts @@ -4,6 +4,7 @@ import { Jurisdiction } from '@deriv/shared'; export type TDxCompanies = ReturnType; export type TMtCompanies = ReturnType; export type TDerivezCompanies = ReturnType; +export type TCTraderCompanies = ReturnType; export const getDxCompanies = () => { const all_config = { @@ -71,8 +72,33 @@ export const getDxCompanies = () => { }; }; +export const getCTraderCompanies = () => { + const all_config = { + account_type: '', + leverage: 500, + short_title: localize('All'), + }; + return { + demo: { + all: { + ctrader_account_type: all_config.account_type, + leverage: all_config.leverage, + title: localize('Demo'), + short_title: all_config.short_title, + }, + }, + real: { + all: { + dxtrade_account_type: all_config.account_type, + leverage: all_config.leverage, + title: localize('All'), + short_title: all_config.short_title, + }, + }, + }; +}; + export const getMtCompanies = (is_eu: boolean) => { - // TODO: Move this to the getDxCompanies for real release and when separating MT5 and DerivX components. const all_config = { account_type: '', leverage: 100, @@ -113,6 +139,12 @@ export const getMtCompanies = (is_eu: boolean) => { title: localize('Demo'), short_title: all_config.short_title, }, + ctrader: { + mt5_account_type: all_config.account_type, + leverage: '500', + title: localize('Demo'), + short_title: localize('cTrader'), + }, synthetic: { mt5_account_type: synthetic_config.account_type, leverage: synthetic_config.leverage, @@ -158,6 +190,12 @@ export const getMtCompanies = (is_eu: boolean) => { title: localize('Swap-Free SVG'), short_title: all_config.short_title, }, + ctrader: { + mt5_account_type: all_config.account_type, + leverage: '500', + title: localize('Real'), + short_title: localize('cTrader'), + }, dxtrade: { mt5_account_type: all_config.account_type, leverage: all_config.leverage, diff --git a/packages/cfd/src/Stores/Modules/CFD/cfd-store.js b/packages/cfd/src/Stores/Modules/CFD/cfd-store.js index a25b74d2372e..2a00f2728dc5 100644 --- a/packages/cfd/src/Stores/Modules/CFD/cfd-store.js +++ b/packages/cfd/src/Stores/Modules/CFD/cfd-store.js @@ -40,6 +40,11 @@ export default class CFDStore extends BaseStore { real: '', }; + ctrader_tokens = { + demo: '', + real: '', + }; + real_synthetic_accounts_existing_data = []; real_financial_accounts_existing_data = []; real_swapfree_accounts_existing_data = []; @@ -67,6 +72,7 @@ export default class CFDStore extends BaseStore { is_cfd_verification_modal_visible: observable, error_type: observable, dxtrade_tokens: observable, + ctrader_tokens: observable, derivez_tokens: observable, account_title: computed, current_list: computed, @@ -93,7 +99,6 @@ export default class CFDStore extends BaseStore { setError: action.bound, setCFDNewAccount: action.bound, setCFDSuccessDialog: action.bound, - storeProofOfAddress: action.bound, getAccountStatus: action.bound, creatMT5Password: action.bound, submitMt5Password: action.bound, @@ -111,8 +116,10 @@ export default class CFDStore extends BaseStore { setJurisdictionSelectedShortcode: action.bound, toggleCFDVerificationModal: action.bound, setDxtradeToken: action.bound, - setDerivezToken: action.bound, + setCTraderToken: action.bound, loadDxtradeTokens: action.bound, + loadCTraderTokens: action.bound, + setDerivezToken: action.bound, loadDerivezTokens: action.bound, }); @@ -133,6 +140,15 @@ export default class CFDStore extends BaseStore { } } ); + + reaction( + () => [this.root_store.client.ctrader_accounts_list], + () => { + if (this.root_store.client.ctrader_accounts_list.length > 0) { + this.loadCTraderTokens(); + } + } + ); } get account_title() { @@ -171,6 +187,11 @@ export default class CFDStore extends BaseStore { ...account, }; }); + this.root_store.client.ctrader_accounts_list.forEach(account => { + list[getAccountListKey(account, CFD_PLATFORMS.CTRADER)] = { + ...account, + }; + }); this.root_store.client.derivez_accounts_list.forEach(account => { // e.g. derivez.real.financial_stp @@ -262,7 +283,6 @@ export default class CFDStore extends BaseStore { async createCFDAccount({ category, platform, type, set_password }) { this.clearCFDError(); - this.setIsAccountBeingCreated(true); this.setAccountType({ category, type, @@ -273,6 +293,32 @@ export default class CFDStore extends BaseStore { } else { this.demoCFDSignup(); } + } else if (platform === CFD_PLATFORMS.CTRADER) { + if (this.account_type.category === 'demo') { + this.setJurisdictionSelectedShortcode('svg'); + this.setIsAccountBeingCreated(true); + } + const account_creation_values = { + platform, + account_type: this.account_type.category, + market_type: this.account_type.type, + }; + const response = await this.openCFDAccount(account_creation_values); + if (!response.error) { + this.setError(false); + this.enableCFDPasswordModal(); + this.setCFDSuccessDialog(true); + + const trading_platform_accounts_list_response = await WS.tradingPlatformAccountsList( + CFD_PLATFORMS.CTRADER + ); + this.root_store.client.responseTradingPlatformAccountsList(trading_platform_accounts_list_response); + WS.transferBetweenAccounts(); + this.setIsAccountBeingCreated(false); + } else { + this.setError(true, response.error); + this.setIsAccountBeingCreated(false); + } } else if (platform === CFD_PLATFORMS.MT5) { if (category === 'real') { this.toggleJurisdictionModal(); @@ -283,6 +329,7 @@ export default class CFDStore extends BaseStore { this.demoCFDSignup(); } } else if (platform === CFD_PLATFORMS.DERIVEZ) { + this.setIsAccountBeingCreated(true); this.setJurisdictionSelectedShortcode('svg'); const values = { platform, @@ -361,7 +408,9 @@ export default class CFDStore extends BaseStore { platform: values.platform, account_type: this.account_type.category, market_type: - this.account_type.type === 'dxtrade' || this.account_type.type === 'derivez' + this.account_type.type === 'dxtrade' || + this.account_type.type === 'cTrader' || + this.account_type.type === 'derivez' ? 'all' : this.account_type.type, company: CFD_PLATFORMS.DERIVEZ ? this.jurisdiction_selected_shortcode : '', @@ -428,41 +477,6 @@ export default class CFDStore extends BaseStore { this.is_cfd_success_dialog_enabled = !!value; } - storeProofOfAddress(file_uploader_ref, values, { setStatus }) { - return new Promise((resolve, reject) => { - setStatus({ msg: '' }); - this.setState({ is_btn_loading: true }); - - WS.setSettings(values).then(data => { - if (data.error) { - setStatus({ msg: data.error.message }); - reject(data); - } else { - this.root_store.fetchAccountSettings(); - // force request to update settings cache since settings have been updated - file_uploader_ref.current.upload().then(api_response => { - if (api_response.warning) { - setStatus({ msg: api_response.message }); - reject(api_response); - } else { - WS.authorized.storage.getAccountStatus().then(({ error, get_account_status }) => { - if (error) { - reject(error); - } - const { identity } = get_account_status.authentication; - const has_poi = !(identity && identity.status === 'none'); - resolve({ - identity, - has_poi, - }); - }); - } - }); - } - }); - }); - } - async getAccountStatus(platform) { const should_load_account_status = (platform === CFD_PLATFORMS.MT5 && this.root_store.client.is_mt5_password_not_set) || @@ -600,7 +614,15 @@ export default class CFDStore extends BaseStore { case CFD_PLATFORMS.DXTRADE: { response = await WS.authorized.send({ trading_platform_deposit: 1, - platform: CFD_PLATFORMS.DXTRADE, + platform, + to_account: this.current_account.account_id, + }); + break; + } + case CFD_PLATFORMS.CTRADER: { + response = await WS.authorized.send({ + trading_platform_deposit: 1, + platform: CFD_PLATFORMS.CTRADER, to_account: this.current_account.account_id, }); break; @@ -637,6 +659,15 @@ export default class CFDStore extends BaseStore { )?.balance; break; } + case CFD_PLATFORMS.CTRADER: { + await WS.authorized + .tradingPlatformAccountsList(CFD_PLATFORMS.CTRADER) + .then(this.root_store.client.responseTradingPlatformAccountsList); + new_balance = this.root_store.client.ctrader_accounts_list.find( + item => item.account_id === this.current_account.account_id + )?.balance; + break; + } case CFD_PLATFORMS.MT5: { await WS.authorized.mt5LoginList().then(this.root_store.client.responseMt5LoginList); @@ -686,6 +717,20 @@ export default class CFDStore extends BaseStore { } } + setCTraderToken(response, server) { + if (!response.error) { + const { ctrader } = response.service_token; + this.ctrader_tokens[server] = ctrader.token; + } + } + + setDerivezToken(response, server) { + if (!response.error) { + const { pandats } = response.service_token; + this.derivez_tokens[server] = pandats.token; + } + } + loadDxtradeTokens() { ['demo', 'real'].forEach(account_type => { const has_existing_account = this.root_store.client.dxtrade_accounts_list.some( @@ -699,11 +744,17 @@ export default class CFDStore extends BaseStore { }); } - setDerivezToken(response, server) { - if (!response.error) { - const { pandats } = response.service_token; - this.derivez_tokens[server] = pandats.token; - } + loadCTraderTokens() { + ['demo', 'real'].forEach(account_type => { + const has_existing_account = this.root_store.client.ctrader_accounts_list.some( + account => account.account_type === account_type + ); + if (!this.ctrader_tokens[account_type] && has_existing_account) { + WS.getServiceToken(CFD_PLATFORMS.CTRADER, account_type).then(response => + this.setCTraderToken(response, account_type) + ); + } + }); } loadDerivezTokens() { diff --git a/packages/cfd/src/sass/cfd-dashboard.scss b/packages/cfd/src/sass/cfd-dashboard.scss index 813626c1348e..1d8f5187d631 100644 --- a/packages/cfd/src/sass/cfd-dashboard.scss +++ b/packages/cfd/src/sass/cfd-dashboard.scss @@ -1298,7 +1298,6 @@ &__maintenance { display: flex; padding-top: 1rem; - align-items: center; &-icon { padding-right: 0.5rem; @@ -1829,160 +1828,6 @@ } } -.account-poa { - &__upload { - &-remove-btn { - position: absolute; - width: 16px; - height: 16px; - top: 8px; - right: 8px; - cursor: pointer; - transition: transform 0.25s linear; - - &:hover { - transform: scale(1.25, 1.25); - } - &--error { - circle { - fill: var(--status-danger); - } - } - &-container { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - } - } - } -} - -.cfd-proof-of-address { - height: 100%; - & .details-form { - display: grid; - grid-template-rows: minmax(10rem, 1fr) 7.2rem; - height: 100%; - .dc-modal-footer { - border-top: 1px solid var(--general-active); - } - } - &__file-upload { - padding-top: 0.8rem; - position: relative; - - .account-poa { - &__upload { - &-section { - margin-top: 0; - display: flex; - } - &-file { - width: 100%; - flex: 1; - height: 24rem; - position: relative; - margin: 0; - - .dc-file-dropzone { - border: 1px dashed var(--border-normal); - max-width: 40rem; - - &__message-subtitle { - font-size: unset; - font-weight: unset; - } - } - @include mobile { - flex: unset; - } - } - &-list { - display: unset; - - .account-poa__upload-box { - display: flex; - flex-direction: unset; - flex: unset; - align-items: center; - justify-content: flex-start; - margin: 0 1.6rem 0.8rem 0; - border: none; - border-radius: 0; - padding: 0; - text-align: unset; - } - } - &-icon { - width: 2.5rem; - margin-bottom: 0; - } - &-item { - min-width: 23.8rem; - width: 100%; - margin-left: 1rem; - font-size: var(--text-size-xxs); - line-height: 1.5; - color: var(--text-prominent); - padding: 0; - } - } - } - } - &__field-area { - max-width: 556px; - margin: 0 auto; - padding: 3rem 0; - - & .dc-dropdown__display-placeholder-text, - & .dc-input__label, - & .dc-dropdown__display { - background: var(--general-main-2); - } - @include mobile { - overflow: auto; - } - } - &__inline-fields { - display: grid; - grid-template-columns: 1fr 1fr 1fr; - grid-gap: 1rem; - - .dc-dropdown-container { - margin-top: 0; - } - } - @include mobile { - .dc-input__label { - background: var(--general-section-2); - } - &__field-area { - padding: 0 1.6rem 3.2rem; - max-height: calc(100% - 0.8rem); - } - &__inline-fields { - display: flex; - flex-direction: column; - - .dc-select-native { - margin-bottom: 3.2rem; - } - } - &__file-upload { - .dc-field--error { - top: 50%; - left: 0; - right: unset; - } - .account-poa__upload-section { - flex-direction: column; - } - } - } -} - .cfd-proof-of-identity { height: 100%; overflow: auto; diff --git a/packages/cfd/src/types/cfd-store.types.ts b/packages/cfd/src/types/cfd-store.types.ts index 57d29e7e4650..ee0939b237fe 100644 --- a/packages/cfd/src/types/cfd-store.types.ts +++ b/packages/cfd/src/types/cfd-store.types.ts @@ -4,13 +4,6 @@ import { TCFDPasswordFormValues } from 'Containers/cfd-password-modal'; import { TDerivezCompanies, TDxCompanies, TMtCompanies } from 'Stores/Modules/CFD/Helpers/cfd-config'; import { FormikHelpers } from 'formik'; -type TStoreProofOfAddressArgs = { - file_uploader_ref: HTMLDivElement | null; - values: { - [key: string]: string; - }; -}; - export type TCFDStore = { setMT5TradeAccount: (arg: T) => void; toggleCFDVerificationModal: () => void; @@ -24,6 +17,10 @@ export type TCFDStore = { demo: string; real: string; }; + ctrader_tokens: { + demo: string; + real: string; + }; mt5_trade_account: Required< DetailsOfEachMT5Loginid & { market_type?: TTradingPlatformAvailableAccount['market_type'] | 'synthetic' } >; @@ -93,5 +90,4 @@ export type TCFDStore = { set_password?: number; platform?: string; }) => void; - storeProofOfAddress: TStoreProofOfAddressArgs; }; diff --git a/packages/components/src/components/autocomplete/autocomplete.tsx b/packages/components/src/components/autocomplete/autocomplete.tsx index 2e8d4aadeee0..232a26c0ad1c 100644 --- a/packages/components/src/components/autocomplete/autocomplete.tsx +++ b/packages/components/src/components/autocomplete/autocomplete.tsx @@ -1,6 +1,6 @@ -import classNames from 'classnames'; import React from 'react'; -import { matchStringByChar, getPosition } from '@deriv/shared'; +import classNames from 'classnames'; +import { matchStringByChar, getPosition, getSearchNotFoundOption } from '@deriv/shared'; import Icon from '../icon'; import Input from '../input'; import DropdownList, { TItem } from '../dropdown-list'; @@ -12,7 +12,7 @@ type TAutocompleteProps = { className: string; disabled?: boolean; dropdown_offset: string; - error: string; + error?: string; has_updating_list?: boolean; hide_list?: boolean; historyValue: string; @@ -22,7 +22,7 @@ type TAutocompleteProps = { list_height: string; list_items: TItem[]; list_portal_id: string; - not_found_text?: string; + not_found_text: string; onBlur?: (e: React.FocusEvent) => void; onHideDropdownList: () => void; onItemSelection: (item: TItem) => void; @@ -59,20 +59,22 @@ const getFilteredItems = (val: string, list: TItem[], should_filter_by_char = fa ); }; const Autocomplete = React.memo((props: TAutocompleteProps) => { + const NO_SEARCH_RESULT = getSearchNotFoundOption(); const { autoComplete, - data_testid, className, + data_testid, dropdown_offset, - historyValue, error, has_updating_list = true, hide_list = false, + historyValue, input_id, is_alignment_top, is_list_visible = false, list_items, list_portal_id, + not_found_text = NO_SEARCH_RESULT, onHideDropdownList, onItemSelection, onScrollStop, @@ -80,7 +82,6 @@ const Autocomplete = React.memo((props: TAutocompleteProps) => { should_filter_by_char, show_list = false, value, - not_found_text = 'No results found', ...other_props } = props; @@ -104,8 +105,8 @@ const Autocomplete = React.memo((props: TAutocompleteProps) => { let new_filtered_items = []; if (is_list_visible) { - if (typeof props.onSearch === 'function') { - new_filtered_items = props.onSearch(value.toLowerCase(), list_items); + if (typeof other_props.onSearch === 'function') { + new_filtered_items = other_props.onSearch(value.toLowerCase(), list_items); } else { new_filtered_items = getFilteredItems(value.toLowerCase(), list_items); } @@ -156,11 +157,11 @@ const Autocomplete = React.memo((props: TAutocompleteProps) => { setStyle(position_style.style); } - }, [should_show_list, is_alignment_top, list_portal_id, filtered_items.length]); + }, [should_show_list, is_alignment_top, list_portal_id, filtered_items?.length]); const handleScrollStop = (e: React.UIEvent) => { // pass onScrollStop func callback when scrolling stops - if (!props.onScrollStop) return; + if (!onScrollStop) return; const element = e.currentTarget; scroll_top_position = element.scrollTop; @@ -168,7 +169,7 @@ const Autocomplete = React.memo((props: TAutocompleteProps) => { clearTimeout(scroll_timeout); } scroll_timeout = setTimeout(() => { - props.onScrollStop?.(); + onScrollStop?.(); }, 150); }; @@ -267,13 +268,13 @@ const Autocomplete = React.memo((props: TAutocompleteProps) => { if (!is_list_visible) setFilteredItems(list_items); if (input_value === '') { - props.onItemSelection?.({ - text: props.not_found_text, + onItemSelection?.({ + text: not_found_text, value: '', }); } - if (typeof props.onBlur === 'function') { - props.onBlur(e); + if (typeof other_props.onBlur === 'function') { + other_props.onBlur(e); } }; @@ -282,29 +283,29 @@ const Autocomplete = React.memo((props: TAutocompleteProps) => { setInputValue((typeof item === 'object' ? item.text : item) || ''); - props.onItemSelection?.(item); + onItemSelection?.(item); }; const showDropdownList = () => { setShouldShowList(true); - props.onShowDropdownList?.(); + onShowDropdownList?.(); }; const hideDropdownList = () => { setShouldShowList(false); - props.onHideDropdownList?.(); + onHideDropdownList?.(); }; const filterList = (e: React.FormEvent) => { const val = (e.target as HTMLInputElement).value.toLowerCase(); let new_filtered_items = []; - if (typeof props.onSearch === 'function') { - new_filtered_items = props.onSearch(val, props.list_items); + if (typeof other_props.onSearch === 'function') { + new_filtered_items = other_props.onSearch(val, list_items); } else { - new_filtered_items = getFilteredItems(val, props.list_items, should_filter_by_char); + new_filtered_items = getFilteredItems(val, list_items, should_filter_by_char); } if (!new_filtered_items.length) { @@ -367,12 +368,12 @@ const Autocomplete = React.memo((props: TAutocompleteProps) => { }} is_visible={should_show_list || is_list_visible} list_items={filtered_items} - list_height={props.list_height} + list_height={other_props.list_height} // Autocomplete must use the `text` property and not the `value`, however DropdownList provides access to both onItemSelection={onSelectItem} setActiveIndex={setActiveIndex} onScrollStop={handleScrollStop} - not_found_text={props.not_found_text} + not_found_text={not_found_text} portal_id={list_portal_id} />
diff --git a/packages/components/src/components/dropdown-list/dropdown-list.tsx b/packages/components/src/components/dropdown-list/dropdown-list.tsx index 1187bf2c23b4..2f4adb4df2c1 100644 --- a/packages/components/src/components/dropdown-list/dropdown-list.tsx +++ b/packages/components/src/components/dropdown-list/dropdown-list.tsx @@ -76,7 +76,7 @@ const ListItem = ({ is_active, is_disabled, index, item, child_ref, onItemSelect const ListItems = React.forwardRef((props, ref) => { const { active_index, list_items, is_object_list, onItemSelection, setActiveIndex, not_found_text } = props; - const is_grouped_list = list_items.some(list_item => typeof list_item === 'object' && !!list_item.group); + const is_grouped_list = list_items?.some(list_item => typeof list_item === 'object' && !!list_item.group); if (is_grouped_list) { const groups: { [key: string]: TItem[] } = {}; @@ -126,7 +126,7 @@ const ListItems = React.forwardRef((props, ref) => { return ( <> - {list_items.length ? ( + {list_items?.length ? ( list_items.map((item, item_idx) => ( { portal_id, } = props; - if (list_items.length && typeof list_items[0] !== 'string' && typeof list_items[0] !== 'object') { + if (list_items?.length && typeof list_items[0] !== 'string' && typeof list_items[0] !== 'object') { throw Error('Dropdown received wrong data structure'); } const is_object = !Array.isArray(list_items) && typeof list_items === 'object'; - const is_string_array = list_items.length && typeof list_items[0] === 'string'; + const is_string_array = list_items?.length && typeof list_items[0] === 'string'; const el_dropdown_list = ( \ No newline at end of file diff --git a/packages/components/src/components/icon/brand/ic-brand-ctrader.svg b/packages/components/src/components/icon/brand/ic-brand-ctrader.svg new file mode 100644 index 000000000000..fc6f589a8802 --- /dev/null +++ b/packages/components/src/components/icon/brand/ic-brand-ctrader.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/src/components/icon/brand/ic-brand-deriv-ez.svg b/packages/components/src/components/icon/brand/ic-brand-deriv-ez.svg index aba5315db1a0..6173fbf60c24 100644 --- a/packages/components/src/components/icon/brand/ic-brand-deriv-ez.svg +++ b/packages/components/src/components/icon/brand/ic-brand-deriv-ez.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/packages/components/src/components/icon/common/ic-installation-google.svg b/packages/components/src/components/icon/common/ic-installation-google.svg index e8f1545fc2e4..5830df93b50b 100644 --- a/packages/components/src/components/icon/common/ic-installation-google.svg +++ b/packages/components/src/components/icon/common/ic-installation-google.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/packages/components/src/components/icon/common/ic-pdf.svg b/packages/components/src/components/icon/common/ic-pdf.svg new file mode 100644 index 000000000000..4e3f480529bb --- /dev/null +++ b/packages/components/src/components/icon/common/ic-pdf.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/src/components/icon/common/ic-poa-verified-dashboard.svg b/packages/components/src/components/icon/common/ic-poa-verified-dashboard.svg deleted file mode 100644 index fda34aad4276..000000000000 --- a/packages/components/src/components/icon/common/ic-poa-verified-dashboard.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/components/src/components/icon/common/ic-unsaved-changes-dashboard.svg b/packages/components/src/components/icon/common/ic-unsaved-changes-dashboard.svg deleted file mode 100644 index f02c5876e488..000000000000 --- a/packages/components/src/components/icon/common/ic-unsaved-changes-dashboard.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/components/src/components/icon/common/ic-upload.svg b/packages/components/src/components/icon/common/ic-upload.svg new file mode 100644 index 000000000000..1835a921838c --- /dev/null +++ b/packages/components/src/components/icon/common/ic-upload.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/src/components/icon/icons.js b/packages/components/src/components/icon/icons.js index c9c96cb73a01..d3689530a574 100644 --- a/packages/components/src/components/icon/icons.js +++ b/packages/components/src/components/icon/icons.js @@ -52,6 +52,8 @@ import './appstore/ic-appstore-wallet-usd-light.svg'; import './appstore/ic-appstore-wallet-usdc-light.svg'; import './appstore/ic-appstore-wallet-usdt-light.svg'; import './appstore/ic-appstore-warning.svg'; +import './brand/ic-brand-ctrader-wordmark.svg'; +import './brand/ic-brand-ctrader.svg'; import './brand/ic-brand-deriv-ez-wordmark.svg'; import './brand/ic-brand-deriv-ez.svg'; import './brand/ic-brand-deriv.svg'; @@ -481,6 +483,7 @@ import './common/ic-pause.svg'; import './common/ic-payment-agent.svg'; import './common/ic-payment-methods-wallet.svg'; import './common/ic-pc.svg'; +import './common/ic-pdf.svg'; import './common/ic-percent-solid.svg'; import './common/ic-phone.svg'; import './common/ic-pix-dark.svg'; @@ -496,7 +499,6 @@ import './common/ic-poa-file-with-address.svg'; import './common/ic-poa-lock-demo.svg'; import './common/ic-poa-lock.svg'; import './common/ic-poa-upload.svg'; -import './common/ic-poa-verified-dashboard.svg'; import './common/ic-poa-verified.svg'; import './common/ic-poi-clear-photo.svg'; import './common/ic-poi-doc-expiry.svg'; @@ -577,9 +579,9 @@ import './common/ic-unknown.svg'; import './common/ic-unlink-apple.svg'; import './common/ic-unlink-facebook.svg'; import './common/ic-unlink-google.svg'; -import './common/ic-unsaved-changes-dashboard.svg'; import './common/ic-unsaved-changes.svg'; import './common/ic-up-down-solid.svg'; +import './common/ic-upload.svg'; import './common/ic-ups-downs.svg'; import './common/ic-user-blocked-outline.svg'; import './common/ic-user-outline.svg'; @@ -716,6 +718,7 @@ import './option/ic-option-raise-fall.svg'; import './option/ic-option-touch-notouch.svg'; import './option/ic-option-up-down-asian.svg'; import './rebranding/ic-rebranding-binary-bot.svg'; +import './rebranding/ic-rebranding-ctrader-dashboard.svg'; import './rebranding/ic-rebranding-deriv-bot-dashboard.svg'; import './rebranding/ic-rebranding-deriv-bot.svg'; import './rebranding/ic-rebranding-deriv-ez-wordmark.svg'; diff --git a/packages/components/src/components/icon/rebranding/ic-rebranding-ctrader-dashboard.svg b/packages/components/src/components/icon/rebranding/ic-rebranding-ctrader-dashboard.svg new file mode 100644 index 000000000000..fc6f589a8802 --- /dev/null +++ b/packages/components/src/components/icon/rebranding/ic-rebranding-ctrader-dashboard.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/src/components/radio-group/radio-group.tsx b/packages/components/src/components/radio-group/radio-group.tsx index 2c5a312e88b8..04fc50b993cf 100644 --- a/packages/components/src/components/radio-group/radio-group.tsx +++ b/packages/components/src/components/radio-group/radio-group.tsx @@ -2,7 +2,13 @@ import React, { ChangeEvent } from 'react'; import classNames from 'classnames'; import Text from '../text'; -type TItem = React.HTMLAttributes & { id?: string; value: string; label: string; disabled: boolean }; +type TItem = React.HTMLAttributes & { + id?: string; + value: string; + label: string; + disabled: boolean; + hidden?: boolean; +}; type TItemWrapper = { should_wrap_items?: boolean; }; @@ -45,46 +51,52 @@ const RadioGroup = ({ return (
{Array.isArray(children) && - children.map(item => ( - - - - ))} + + + + {item.props.label} + + + + ))}
); }; -const Item = ({ children, ...props }: React.PropsWithChildren) =>
{children}
; +const Item = ({ children, hidden = false, ...props }: React.PropsWithChildren) => ( + +); RadioGroup.Item = Item; diff --git a/packages/components/stories/icon/icons.js b/packages/components/stories/icon/icons.js index 9baa418d97f9..cdba2954dcf0 100644 --- a/packages/components/stories/icon/icons.js +++ b/packages/components/stories/icon/icons.js @@ -54,9 +54,11 @@ export const icons = 'IcAppstoreWalletUsdLight', 'IcAppstoreWalletUsdcLight', 'IcAppstoreWalletUsdtLight', - 'IcAppstoreWarning' + 'IcAppstoreWarning', ], 'brand': [ + 'IcBrandCtraderWordmark', + 'IcBrandCtrader', 'IcBrandDerivEzWordmark', 'IcBrandDerivEz', 'IcBrandDeriv', @@ -64,7 +66,7 @@ export const icons = 'IcBrandDmt5FinancialStp', 'IcBrandDmt5Financial', 'IcBrandDmt5Synthetics', - 'IcBrandDxtradeWordmark' + 'IcBrandDxtradeWordmark', ], 'cashier': [ 'IcCashierAdd', @@ -238,7 +240,7 @@ export const icons = 'IcCashierWyreLight', 'IcCashierZenithbankDark', 'IcCashierZenithbankLight', - 'IcCashier' + 'IcCashier', ], 'common': [ 'IcAccountCross', @@ -490,6 +492,7 @@ export const icons = 'IcPaymentAgent', 'IcPaymentMethodsWallet', 'IcPc', + 'IcPdf', 'IcPercentSolid', 'IcPhone', 'IcPixDark', @@ -505,7 +508,6 @@ export const icons = 'IcPoaLockDemo', 'IcPoaLock', 'IcPoaUpload', - 'IcPoaVerifiedDashboard', 'IcPoaVerified', 'IcPoiClearPhoto', 'IcPoiDocExpiry', @@ -586,9 +588,9 @@ export const icons = 'IcUnlinkApple', 'IcUnlinkFacebook', 'IcUnlinkGoogle', - 'IcUnsavedChangesDashboard', 'IcUnsavedChanges', 'IcUpDownSolid', + 'IcUpload', 'IcUpsDowns', 'IcUserBlockedOutline', 'IcUserOutline', @@ -611,7 +613,7 @@ export const icons = 'IcWip', 'IcZingpay', 'IcZoomIn', - 'IcZoomOut' + 'IcZoomOut', ], 'contract': [ 'IcContractBarrier', @@ -629,7 +631,7 @@ export const icons = 'IcContractStartTimeCircle', 'IcContractStartTime', 'IcContractStrike', - 'IcContractTarget' + 'IcContractTarget', ], 'currency': [ 'IcCurrencyAud', @@ -653,10 +655,10 @@ export const icons = 'IcCurrencyUsdc', 'IcCurrencyUsdk', 'IcCurrencyUst', - 'IcCurrencyVirtual' + 'IcCurrencyVirtual', ], 'derivez': [ - 'IcDerivez' + 'IcDerivez', ], 'dxtrade': [ 'IcDxtradeDerivX', @@ -673,7 +675,7 @@ export const icons = 'IcDxtradeFinancialPlatform', 'IcDxtradeFinancial', 'IcDxtradeOnePassword', - 'IcDxtradeSyntheticPlatform' + 'IcDxtradeSyntheticPlatform', ], 'flag': [ 'IcFlagDe', @@ -690,7 +692,7 @@ export const icons = 'IcFlagUk', 'IcFlagVi', 'IcFlagZhCn', - 'IcFlagZhTw' + 'IcFlagZhTw', ], 'mt5': [ 'IcMt5Acuity', @@ -719,7 +721,7 @@ export const icons = 'IcMt5SyntheticDashboard', 'IcMt5SyntheticIndices', 'IcMt5SyntheticPlatform', - 'IcMt5TradeTypes' + 'IcMt5TradeTypes', ], 'option': [ 'IcOptionAccumulators', @@ -737,10 +739,11 @@ export const icons = 'IcOptionOverUnder', 'IcOptionRaiseFall', 'IcOptionTouchNotouch', - 'IcOptionUpDownAsian' + 'IcOptionUpDownAsian', ], 'rebranding': [ 'IcRebrandingBinaryBot', + 'IcRebrandingCtraderDashboard', 'IcRebrandingDerivBotDashboard', 'IcRebrandingDerivBot', 'IcRebrandingDerivEzWordmark', @@ -758,7 +761,7 @@ export const icons = 'IcRebrandingDxtrade', 'IcRebrandingMt5Logo', 'IcRebrandingSmarttraderDashboard', - 'IcRebrandingSmarttrader' + 'IcRebrandingSmarttrader', ], 'stock': [ 'IcStockAdidasSalomon', @@ -808,7 +811,7 @@ export const icons = 'IcStockVisa', 'IcStockWallMart', 'IcStockWaltDisney', - 'IcStockZoom' + 'IcStockZoom', ], 'tradetype': [ 'IcTradetypeAccu', @@ -847,7 +850,7 @@ export const icons = 'IcTradetypeTurbosshort', 'IcTradetypeUpordown', 'IcTradetypeVanillaLongCall', - 'IcTradetypeVanillaLongPut' + 'IcTradetypeVanillaLongPut', ], 'underlying': [ 'IcUnderlying1HZ100V', @@ -1006,7 +1009,7 @@ export const icons = 'IcUnderlyingWLDEUR', 'IcUnderlyingWLDGBP', 'IcUnderlyingWLDUSD', - 'IcUnderlyingWLDXAU' + 'IcUnderlyingWLDXAU', ], 'wallet': [ 'IcWalletClearFunds', @@ -1042,6 +1045,6 @@ export const icons = 'IcWalletWebmoneyLight', 'IcWalletWebmoney', 'IcWalletZingpayDark', - 'IcWalletZingpayLight' - ] -} \ No newline at end of file + 'IcWalletZingpayLight', + ], +} diff --git a/packages/components/utils/helper.js b/packages/components/utils/helper.js index d4489fad3842..0f2b06c10f29 100644 --- a/packages/components/utils/helper.js +++ b/packages/components/utils/helper.js @@ -18,6 +18,14 @@ const getPascalCase = str => { ); }; +const getSnakeCase = str => { + if (!str) return str; + return str + .replace(/([a-z0-9])([A-Z])/g, '$1_$2') // get all lowercase letters that are near to uppercase ones + .replace(/[\s]+/g, '_') // replace all spaces and low dash + .toLowerCase(); +}; + const getFileNameFromPath = path => path.match(/([^/]*)\/*$/)[1].replace('.svg', ''); const getEnglishCharacters = input => @@ -28,8 +36,9 @@ const getEnglishCharacters = input => .join(''); module.exports = { - getPascalCase, + getEnglishCharacters, getFileNameFromPath, getKebabCase, - getEnglishCharacters, + getPascalCase, + getSnakeCase, }; diff --git a/packages/core/src/App/Components/Elements/Modals/deriv-real-account-required-modal.jsx b/packages/core/src/App/Components/Elements/Modals/deriv-real-account-required-modal.jsx index c2880096e4cf..4d9eb8c8c25b 100644 --- a/packages/core/src/App/Components/Elements/Modals/deriv-real-account-required-modal.jsx +++ b/packages/core/src/App/Components/Elements/Modals/deriv-real-account-required-modal.jsx @@ -37,7 +37,7 @@ const DerivRealAccountRequiredModal = ({ is_closed_on_confirm is_visible={is_open} > - {localize('A Deriv account will allow you to fund (and withdraw from) your MT5 account(s).')} + {localize('A Deriv account will allow you to fund (and withdraw from) your CFDs account(s).')} ); }; diff --git a/packages/core/src/App/Containers/RealAccountSignup/account-wizard.jsx b/packages/core/src/App/Containers/RealAccountSignup/account-wizard.jsx index c7797b582945..a7512e6ab496 100644 --- a/packages/core/src/App/Containers/RealAccountSignup/account-wizard.jsx +++ b/packages/core/src/App/Containers/RealAccountSignup/account-wizard.jsx @@ -219,6 +219,14 @@ const AccountWizard = props => { delete clone?.tax_identification_confirm; delete clone?.agreed_tnc; delete clone?.agreed_tos; + + // BE does not accept empty strings for TIN + // so we remove it from the payload if it is empty in case of optional TIN field + // as the value will be available from the form_values + if (clone?.tax_identification_number?.length === 0) { + delete clone.tax_identification_number; + } + clone = processInputData(clone); props.setRealAccountFormData(clone); if (payload) { diff --git a/packages/core/src/App/Containers/app-notification-messages.jsx b/packages/core/src/App/Containers/app-notification-messages.jsx index 6580f03e612a..fbc5a2cb1053 100644 --- a/packages/core/src/App/Containers/app-notification-messages.jsx +++ b/packages/core/src/App/Containers/app-notification-messages.jsx @@ -146,7 +146,6 @@ const AppNotificationMessages = ({ 'tnc', 'trustpilot', 'unwelcome', - 'mt5_notification', ].includes(message.key) || message.type === 'p2p_completed_order' : true; diff --git a/packages/core/src/Constants/cfd-text.js b/packages/core/src/Constants/cfd-text.js index 264d84db7f70..cdb715f5f9ff 100644 --- a/packages/core/src/Constants/cfd-text.js +++ b/packages/core/src/Constants/cfd-text.js @@ -4,6 +4,7 @@ export const CFD_TEXT = { dxtrade: () => localize('Deriv X'), mt5: () => localize('MT5'), mt5_cfds: () => localize('MT5 CFDs'), + ctrader: () => localize('cTrader'), cfd: () => localize('CFDs'), synthetic: () => localize('Derived'), synthetic_demo: () => localize('Derived Demo'), diff --git a/packages/core/src/Stores/Helpers/client-notifications.js b/packages/core/src/Stores/Helpers/client-notifications.js index c9aa80f788fd..f453a5776b13 100644 --- a/packages/core/src/Stores/Helpers/client-notifications.js +++ b/packages/core/src/Stores/Helpers/client-notifications.js @@ -72,6 +72,5 @@ export const priority_toast_messages = [ 'p2p_daily_limit_increase', 'authenticate', 'notify_financial_assessment', - 'mt5_notification', ...maintenance_notifications, ]; diff --git a/packages/core/src/Stores/client-store.js b/packages/core/src/Stores/client-store.js index 5a6ead2db315..f70bd4181740 100644 --- a/packages/core/src/Stores/client-store.js +++ b/packages/core/src/Stores/client-store.js @@ -52,6 +52,7 @@ export default class ClientStore extends BaseStore { email; accounts = {}; trading_platform_available_accounts = []; + ctrader_available_accounts = []; derivez_available_accounts = []; pre_switch_broadcast = false; switched = ''; @@ -103,6 +104,7 @@ export default class ClientStore extends BaseStore { mt5_login_list = []; mt5_login_list_error = null; dxtrade_accounts_list = []; + ctrader_accounts_list = []; derivez_accounts_list = []; dxtrade_accounts_list_error = null; dxtrade_disabled_signup_types = { real: false, demo: false }; @@ -169,6 +171,7 @@ export default class ClientStore extends BaseStore { email: observable, accounts: observable, trading_platform_available_accounts: observable, + ctrader_available_accounts: observable, derivez_available_accounts: observable, pre_switch_broadcast: observable, switched: observable, @@ -205,6 +208,7 @@ export default class ClientStore extends BaseStore { mt5_login_list: observable, mt5_login_list_error: observable, dxtrade_accounts_list: observable, + ctrader_accounts_list: observable, derivez_accounts_list: observable, dxtrade_accounts_list_error: observable, dxtrade_disabled_signup_types: observable, @@ -387,6 +391,7 @@ export default class ClientStore extends BaseStore { responseTradingPlatformAvailableAccounts: action.bound, responseDerivezAvailableAccounts: action.bound, responseTradingPlatformAccountsList: action.bound, + responseCTraderAvailableAccounts: action.bound, responseStatement: action.bound, getChangeableFields: action.bound, syncWithLegacyPlatforms: action.bound, @@ -630,6 +635,10 @@ export default class ClientStore extends BaseStore { return this.dxtrade_accounts_list.some(account => account.account_type === 'real'); } + get has_real_ctrader_login() { + return this.ctrader_accounts_list.some(account => account.account_type === 'real'); + } + get has_real_derivez_login() { return this.derivez_accounts_list.some(account => account.account_type === 'real'); } @@ -1717,6 +1726,8 @@ export default class ClientStore extends BaseStore { WS.tradingPlatformAvailableAccounts(CFD_PLATFORMS.MT5).then(this.responseTradingPlatformAvailableAccounts); WS.tradingPlatformAccountsList(CFD_PLATFORMS.DXTRADE).then(this.responseTradingPlatformAccountsList); + WS.tradingPlatformAccountsList(CFD_PLATFORMS.CTRADER).then(this.responseTradingPlatformAccountsList); + WS.tradingPlatformAvailableAccounts(CFD_PLATFORMS.CTRADER).then(this.responseCTraderAvailableAccounts); WS.tradingServers(CFD_PLATFORMS.DXTRADE).then(this.responseDxtradeTradingServers); WS.tradingPlatformAccountsList(CFD_PLATFORMS.DERIVEZ).then(this.responseTradingPlatformAccountsList); WS.tradingPlatformAccountsList(CFD_PLATFORMS.DERIVEZ).then(this.responseDerivezAvailableAccounts); @@ -2104,6 +2115,7 @@ export default class ClientStore extends BaseStore { this.accounts = {}; this.mt5_login_list = []; this.dxtrade_accounts_list = []; + this.ctrader_accounts_list = []; this.derivez_accounts_list = []; this.landing_companies = {}; localStorage.removeItem('readScamMessage'); @@ -2493,7 +2505,7 @@ export default class ClientStore extends BaseStore { this.setMT5DisabledSignupTypes({ [account_type]: true, }); - if (platform === CFD_PLATFORMS.DERIVEZ) { + if (platform === CFD_PLATFORMS.DERIVEZ || platform === CFD_PLATFORMS.CTRADER) { this.setCFDDisabledSignupTypes(platform, { [account_type]: true, }); @@ -2538,6 +2550,12 @@ export default class ClientStore extends BaseStore { } } + responseCTraderAvailableAccounts(response) { + if (!response.error) { + this.ctrader_available_accounts = response.trading_platform_available_accounts; + } + } + responseDerivezAvailableAccounts(response) { if (!response.error) { this.derivez_available_accounts = response.trading_platform_accounts; diff --git a/packages/core/src/Stores/notification-store.js b/packages/core/src/Stores/notification-store.js index dcb0c91f3473..41ee9acd1503 100644 --- a/packages/core/src/Stores/notification-store.js +++ b/packages/core/src/Stores/notification-store.js @@ -586,9 +586,6 @@ export default class NotificationStore extends BaseStore { this.addNotificationMessage(this.client_notifications.svg_poi_expired); } } - if (client && this.root_store.client.mt5_login_list.length > 0) { - this.addNotificationMessage(this.client_notifications.mt5_notification); - } } if (!is_eu && isMultiplierContract(selected_contract_type) && current_language === 'EN' && is_logged_in) { diff --git a/packages/core/src/Stores/traders-hub-store.js b/packages/core/src/Stores/traders-hub-store.js index b25c4c752287..836dfdb715de 100644 --- a/packages/core/src/Stores/traders-hub-store.js +++ b/packages/core/src/Stores/traders-hub-store.js @@ -10,6 +10,7 @@ export default class TradersHubStore extends BaseStore { available_cfd_accounts = []; available_mt5_accounts = []; available_dxtrade_accounts = []; + available_ctrader_accounts = []; available_derivez_accounts = []; combined_cfd_mt5_accounts = []; selected_account_type; @@ -37,6 +38,7 @@ export default class TradersHubStore extends BaseStore { account_type_card: observable, available_cfd_accounts: observable, available_dxtrade_accounts: observable, + available_ctrader_accounts: observable, available_derivez_accounts: observable, available_mt5_accounts: observable, available_platforms: observable, @@ -59,6 +61,7 @@ export default class TradersHubStore extends BaseStore { getAccount: action.bound, getAvailableCFDAccounts: action.bound, getAvailableDxtradeAccounts: action.bound, + getAvailableCTraderAccounts: action.bound, getAvailableDerivEzAccounts: action.bound, getExistingAccounts: action.bound, handleTabItemClick: action.bound, @@ -106,6 +109,7 @@ export default class TradersHubStore extends BaseStore { this.root_store.client.is_switching, this.root_store.client.mt5_login_list, this.root_store.client.dxtrade_accounts_list, + this.root_store.client.ctrader_accounts_list, this.root_store.client.derivez_accounts_list, this.is_demo_low_risk, this.root_store.modules?.cfd?.current_list, @@ -393,6 +397,7 @@ export default class TradersHubStore extends BaseStore { }; }); this.getAvailableDxtradeAccounts(); + this.getAvailableCTraderAccounts(); this.getAvailableDerivEzAccounts(); this.getAvailableMt5Accounts(); this.setCombinedCFDMT5Accounts(); @@ -459,6 +464,24 @@ export default class TradersHubStore extends BaseStore { account => account.platform === CFD_PLATFORMS.DXTRADE ); } + getAvailableCTraderAccounts() { + if (this.CFDs_restricted_countries || this.financial_restricted_countries) { + this.available_ctrader_accounts = []; + return; + } + + if (this.is_eu_user && !this.is_demo_low_risk) { + this.available_ctrader_accounts = this.available_cfd_accounts.filter( + account => + ['EU', 'All'].some(region => region === account.availability) && + account.platform === CFD_PLATFORMS.CTRADER + ); + return; + } + this.available_ctrader_accounts = this.available_cfd_accounts.filter( + account => account.platform === CFD_PLATFORMS.CTRADER + ); + } getAvailableDerivEzAccounts() { if (this.CFDs_restricted_countries || this.financial_restricted_countries) { @@ -497,7 +520,10 @@ export default class TradersHubStore extends BaseStore { if (platform === CFD_PLATFORMS.DXTRADE && market_type === 'all') { return key.startsWith(`${platform}.${selected_account_type}.${platform}@${market_type}`); } - if (platform === CFD_PLATFORMS.DERIVEZ && market_type === 'all') { + if ( + platform === CFD_PLATFORMS.DERIVEZ || + (platform === CFD_PLATFORMS.CTRADER && market_type === 'all') + ) { return key.startsWith(`${platform}.${selected_account_type}.${platform}@${market_type}`); } if ( @@ -570,14 +596,17 @@ export default class TradersHubStore extends BaseStore { } else { await createCFDAccount({ ...account_type, platform }); } + if (platform !== CFD_PLATFORMS.CTRADER) { + enableCFDPasswordModal(); + } + createCFDAccount({ ...account_type, platform }); } async openRealAccount(account_type, platform) { const { client, modules } = this.root_store; const { has_active_real_account } = client; const { createCFDAccount, enableCFDPasswordModal, toggleJurisdictionModal } = modules.cfd; - - if (has_active_real_account && platform === CFD_PLATFORMS.MT5) { + if ((has_active_real_account && platform === CFD_PLATFORMS.MT5) || platform === CFD_PLATFORMS.CTRADER) { toggleJurisdictionModal(); } else if (platform !== CFD_PLATFORMS.DERIVEZ) { enableCFDPasswordModal(); diff --git a/packages/core/src/sass/app.scss b/packages/core/src/sass/app.scss index cee96bc68af2..d0ea8a835811 100644 --- a/packages/core/src/sass/app.scss +++ b/packages/core/src/sass/app.scss @@ -49,6 +49,7 @@ @import 'app/_common/components/notification-close-mx-mlt'; @import 'app/_common/components/notification-promo'; @import 'app/_common/components/onfido-container'; +@import 'app/_common/components/cfd-poa'; // Modules @import 'app/modules/page-404'; @import 'app/modules/account-closed'; diff --git a/packages/core/src/sass/app/_common/components/cfd-poa.scss b/packages/core/src/sass/app/_common/components/cfd-poa.scss new file mode 100644 index 000000000000..718b57fd1051 --- /dev/null +++ b/packages/core/src/sass/app/_common/components/cfd-poa.scss @@ -0,0 +1,24 @@ +.cfd-proof-of-address { + .dc-themed-scrollbars { + padding: 0.8rem 3rem; + } + + .dc-input { + margin-bottom: 0.8rem; + } + + .account__scrollbars_container--grid-layout { + @include mobile { + padding: 2.4rem 1.6rem; + overflow-y: auto; + } + } + + .dc-form-submit-button { + background-color: var(--general-main-1) !important; + } + + .dc-autocomplete { + margin-bottom: 0 !important; + } +} diff --git a/packages/core/src/sass/app/_common/components/onfido-container.scss b/packages/core/src/sass/app/_common/components/onfido-container.scss index c2634c000291..10414683899e 100644 --- a/packages/core/src/sass/app/_common/components/onfido-container.scss +++ b/packages/core/src/sass/app/_common/components/onfido-container.scss @@ -209,12 +209,6 @@ &-side-note { grid-area: section-side-note; - .account-poa__upload-box-dashboard { - @include mobile { - align-items: flex-start; - } - } - @include mobile { margin-top: unset; width: 100%; @@ -262,22 +256,163 @@ } } } + + .additional-field { + margin-top: 1.6rem; + + @include mobile { + margin-top: 1rem; + } + } } .account-form_poa { - .account-form__section { - align-items: unset; + .account { + &-form { + &__section { + align-items: unset; - &-side-note { - width: 26rem; + &-side-note { + width: 26rem; + } + + &-content { + width: 40rem; + + @include mobile { + width: 100%; + } + } + } + + &__fieldset { + max-width: unset; + margin-top: 1.6rem; + display: flex; + flex-direction: column; + gap: 3.2rem; + } } - &-content { - width: 40rem; + &__scrollbars_container { + padding-top: 0; + padding-left: 0; + padding-bottom: 0; + + &--grid-layout { + @include mobile { + padding-top: 2.4rem; + } + } + } + } + + .files-description { + &__title { + margin-bottom: 1.6rem; + } + + li { + list-style-type: initial; + margin-left: 1.6rem; + } + } + + &-submit-error { + justify-content: left; + } +} + +.file-uploader { + &__container { + @include desktop { + margin: 1.6rem 0; + padding: 1.6rem 2.4rem; + border-radius: $BORDER_RADIUS * 2; + border: 1px solid var(--border-normal); + } + } + + &__file { + &-dropzone-wrapper { + flex: 1; + height: 13.2rem; + position: relative; + + .dc-file-dropzone { + border-radius: $BORDER_RADIUS * 2; + + &__message { + max-width: unset; + + &-subtitle { + font-size: 1.4rem; + font-weight: bold; + display: flex; + flex-direction: column; + gap: 0.8rem; + margin-top: 1.6rem; + } + } + + @include mobile { + border: 1px dashed var(--icon-grey-background); + } + } + } + + &-title { + margin: 2.4rem 0 1.6rem; + } + + &-supported-formats { + display: flex; + justify-content: space-between; + margin: 1.6rem 0 2.4rem; @include mobile { - width: 100%; + margin-bottom: 1.6rem; } + + span { + @include mobile { + max-width: 14rem; + } + } + } + + @include mobile { + flex: unset; + margin-bottom: 2.4rem; + height: 15rem; + } + } + + &__remove-btn { + position: absolute; + width: 1.6rem; + height: 1.6rem; + top: 0.8rem; + right: 0.8rem; + cursor: pointer; + transition: transform 0.25s linear; + + &:hover { + transform: scale(1.25, 1.25); + } + + &--error { + circle { + fill: var(--status-danger); + } + } + + &-container { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; } } } diff --git a/packages/hooks/src/__tests__/useCFDAllAccounts.spec.tsx b/packages/hooks/src/__tests__/useCFDAllAccounts.spec.tsx index 74a602c08adb..06479e81c4d1 100644 --- a/packages/hooks/src/__tests__/useCFDAllAccounts.spec.tsx +++ b/packages/hooks/src/__tests__/useCFDAllAccounts.spec.tsx @@ -12,7 +12,7 @@ describe('useCFDAllAccounts', () => { ); const { result } = renderHook(() => useCFDAllAccounts(), { wrapper }); - expect(result.current.length).toBe(0); + expect(result.current).toHaveLength(0); }); test('should return proper data when client has MT5 accounts', async () => { @@ -33,7 +33,7 @@ describe('useCFDAllAccounts', () => { ); const { result } = renderHook(() => useCFDAllAccounts(), { wrapper }); - expect(result.current.length).toBe(1); + expect(result.current).toHaveLength(1); }); test('should return proper data when client has dxtrade accounts', async () => { @@ -54,10 +54,31 @@ describe('useCFDAllAccounts', () => { ); const { result } = renderHook(() => useCFDAllAccounts(), { wrapper }); - expect(result.current.length).toBe(1); + expect(result.current).toHaveLength(1); }); - test('should return proper data when client has both MT5 and dxtrade accounts', async () => { + test('should return proper data when client has ctrader accounts', async () => { + const mock = mockStore({ + client: { + ctrader_accounts_list: [ + { + account_type: 'real', + balance: 1000, + currency: 'USD', + }, + ], + }, + }); + + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); + const { result } = renderHook(() => useCFDAllAccounts(), { wrapper }); + + expect(result.current).toHaveLength(1); + }); + + test('should return proper data when client has MT5, ctrader and dxtrade accounts', async () => { const mock = mockStore({ client: { mt5_login_list: [ @@ -67,6 +88,13 @@ describe('useCFDAllAccounts', () => { currency: 'USD', }, ], + ctrader_accounts_list: [ + { + account_type: 'real', + balance: 1000, + currency: 'USD', + }, + ], dxtrade_accounts_list: [ { account_type: 'real', @@ -82,6 +110,6 @@ describe('useCFDAllAccounts', () => { ); const { result } = renderHook(() => useCFDAllAccounts(), { wrapper }); - expect(result.current.length).toBe(2); + expect(result.current).toHaveLength(3); }); }); diff --git a/packages/hooks/src/__tests__/useHasP2PSupportedCurrencies.spec.tsx b/packages/hooks/src/__tests__/useHasP2PSupportedCurrencies.spec.tsx index 244651c91b97..54d67556d18a 100644 --- a/packages/hooks/src/__tests__/useHasP2PSupportedCurrencies.spec.tsx +++ b/packages/hooks/src/__tests__/useHasP2PSupportedCurrencies.spec.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { APIProvider, useFetch } from '@deriv/api'; +import { APIProvider } from '@deriv/api'; import { StoreProvider, mockStore } from '@deriv/stores'; import { renderHook } from '@testing-library/react-hooks'; import useHasP2PSupportedCurrencies from '../useHasP2PSupportedCurrencies'; diff --git a/packages/hooks/src/__tests__/useIsP2PEnabled.spec.tsx b/packages/hooks/src/__tests__/useIsP2PEnabled.spec.tsx index 861279dc30b0..f89406a11358 100644 --- a/packages/hooks/src/__tests__/useIsP2PEnabled.spec.tsx +++ b/packages/hooks/src/__tests__/useIsP2PEnabled.spec.tsx @@ -1,4 +1,4 @@ -import * as React from 'react'; +import React from 'react'; import { APIProvider, useFetch } from '@deriv/api'; import { StoreProvider, mockStore } from '@deriv/stores'; import { renderHook } from '@testing-library/react-hooks'; diff --git a/packages/hooks/src/__tests__/useP2PAdvertiserPaymentMethods.spec.tsx b/packages/hooks/src/__tests__/useP2PAdvertiserPaymentMethods.spec.tsx new file mode 100644 index 000000000000..45a643d566f7 --- /dev/null +++ b/packages/hooks/src/__tests__/useP2PAdvertiserPaymentMethods.spec.tsx @@ -0,0 +1,231 @@ +import * as React from 'react'; +import { APIProvider, useFetch, useRequest } from '@deriv/api'; +import { StoreProvider, mockStore } from '@deriv/stores'; +import { renderHook } from '@testing-library/react-hooks'; +import useP2PAdvertiserPaymentMethods from '../useP2PAdvertiserPaymentMethods'; + +jest.mock('@deriv/api', () => ({ + ...jest.requireActual('@deriv/api'), + useFetch: jest.fn(), + useRequest: jest.fn(() => ({ mutate: jest.fn() })), +})); + +const mockUseFetch = useFetch as jest.MockedFunction>; +const mockUseRequest = useRequest as jest.MockedFunction>; + +describe('useP2PAdvertiserPaymentMethods', () => { + test('should return undefined when p2p_advertiser_payment_methods is not available', () => { + const mock = mockStore({}); + + // @ts-expect-error need to come up with a way to mock the return type of useFetch + mockUseFetch.mockReturnValue({ + data: {}, + }); + + const wrapper = ({ children }: { children: JSX.Element }) => ( + + {children}{' '} + + ); + + const { result } = renderHook(() => useP2PAdvertiserPaymentMethods(), { wrapper }); + + expect(result.current.data).toBeUndefined(); + }); + + test('should return the expected data when p2p_advertiser_payment_methods is available', () => { + const mock = mockStore({}); + + // @ts-expect-error need to come up with a way to mock the return type of useFetch + mockUseFetch.mockReturnValue({ + data: { + p2p_advertiser_payment_methods: { + 1: { + display_name: 'Skrill', + fields: { + account: { + display_name: 'Email or phone number', + required: 1, + type: 'text', + value: '1234567890', + }, + instructions: { + display_name: 'Instructions', + required: 0, + type: 'memo', + value: '', + }, + }, + is_enabled: 1, + method: 'skrill', + type: 'ewallet', + used_by_adverts: ['67'], + used_by_orders: ['49', '53'], + }, + }, + }, + }); + + const wrapper = ({ children }: { children: JSX.Element }) => ( + + {children}{' '} + + ); + + const { result } = renderHook(() => useP2PAdvertiserPaymentMethods(), { wrapper }); + + expect(result.current.data?.[0]).toHaveProperty('display_name', 'Skrill'); + expect(result.current.data?.[0]).toHaveProperty('fields'); + expect(result.current.data?.[0]).toHaveProperty('is_enabled', 1); + expect(result.current.data?.[0]).toHaveProperty('type', 'ewallet'); + expect(result.current.data?.[0]).toHaveProperty('icon', 'IcCashierEwallet'); + expect(result.current.data?.[0]).toHaveProperty('id', '1'); + }); + + test('should create a new p2p_advertiser_payment_methods', () => { + const mock = mockStore({}); + + // @ts-expect-error need to come up with a way to mock the return type of useRequest + mockUseRequest.mockReturnValue({ mutate: jest.fn() }); + + const wrapper = ({ children }: { children: JSX.Element }) => ( + + {children}{' '} + + ); + + const { result } = renderHook(() => useP2PAdvertiserPaymentMethods(), { wrapper }); + + result.current.create({ + account: '1231', + instructions: '', + method: 'skrill', + }); + + expect(mockUseRequest('p2p_advertiser_payment_methods').mutate).toBeCalledWith({ + payload: { + create: [ + { + account: '1231', + instructions: '', + method: 'skrill', + }, + ], + }, + }); + }); + + test('should update a p2p_advertiser_payment_methods', () => { + const mock = mockStore({}); + + // @ts-expect-error need to come up with a way to mock the return type of useFetch + mockUseFetch.mockReturnValue({ + data: { + p2p_advertiser_payment_methods: { + 1: { + display_name: 'Skrill', + fields: { + account: { + display_name: 'Email or phone number', + required: 1, + type: 'text', + value: '1234567890', + }, + instructions: { + display_name: 'Instructions', + required: 0, + type: 'memo', + value: '', + }, + }, + is_enabled: 1, + method: 'skrill', + type: 'ewallet', + used_by_adverts: ['67'], + used_by_orders: ['49', '53'], + }, + }, + }, + }); + // @ts-expect-error need to come up with a way to mock the return type of useRequest + mockUseRequest.mockReturnValue({ mutate: jest.fn() }); + + const wrapper = ({ children }: { children: JSX.Element }) => ( + + {children}{' '} + + ); + + const { result } = renderHook(() => useP2PAdvertiserPaymentMethods(), { wrapper }); + + result.current.update('1', { + account: '1231', + instructions: '', + method: 'skrill', + }); + + expect(mockUseRequest('p2p_advertiser_payment_methods').mutate).toBeCalledWith({ + payload: { + update: { + 1: { + account: '1231', + instructions: '', + method: 'skrill', + }, + }, + }, + }); + }); + + test('should delete a p2p_advertiser_payment_methods', () => { + const mock = mockStore({}); + + // @ts-expect-error need to come up with a way to mock the return type of useFetch + mockUseFetch.mockReturnValue({ + data: { + p2p_advertiser_payment_methods: { + 1: { + display_name: 'Skrill', + fields: { + account: { + display_name: 'Email or phone number', + required: 1, + type: 'text', + value: '1234567890', + }, + instructions: { + display_name: 'Instructions', + required: 0, + type: 'memo', + value: '', + }, + }, + is_enabled: 1, + method: 'skrill', + type: 'ewallet', + used_by_adverts: ['67'], + used_by_orders: ['49', '53'], + }, + }, + }, + }); + // @ts-expect-error need to come up with a way to mock the return type of useRequest + mockUseRequest.mockReturnValue({ mutate: jest.fn() }); + + const wrapper = ({ children }: { children: JSX.Element }) => ( + + {children}{' '} + + ); + + const { result } = renderHook(() => useP2PAdvertiserPaymentMethods(), { wrapper }); + + result.current.delete(1); + + expect(mockUseRequest('p2p_advertiser_payment_methods').mutate).toBeCalledWith({ + payload: { + delete: [1], + }, + }); + }); +}); diff --git a/packages/hooks/src/__tests__/useP2PConfig.spec.tsx b/packages/hooks/src/__tests__/useP2PConfig.spec.tsx new file mode 100644 index 000000000000..7f406799e77f --- /dev/null +++ b/packages/hooks/src/__tests__/useP2PConfig.spec.tsx @@ -0,0 +1,69 @@ +import React from 'react'; +import { APIProvider, useFetch } from '@deriv/api'; +import { StoreProvider, mockStore } from '@deriv/stores'; +import { renderHook } from '@testing-library/react-hooks'; +import useP2PConfig from '../useP2PConfig'; + +type TWrapper = { + children: JSX.Element; +}; + +jest.mock('@deriv/api', () => ({ + ...jest.requireActual('@deriv/api'), + useFetch: jest.fn(), +})); + +const mockUseFetch = useFetch as jest.MockedFunction>; + +describe('useP2PConfig', () => { + it('should return undefined if there is no response', () => { + const mock_store = mockStore({}); + // @ts-expect-error need to come up with a way to mock the return type of useFetch + mockUseFetch.mockReturnValue({}); + + const wrapper = ({ children }: TWrapper) => ( + + {children} + + ); + + const { result } = renderHook(() => useP2PConfig(), { wrapper }); + expect(result.current.data).toBe(undefined); + }); + + it('should return the p2p_config object from response', () => { + const mock_store = mockStore({ + client: { + currency: 'USD', + }, + }); + + mockUseFetch.mockReturnValue({ + data: { + website_status: { + // @ts-expect-error need to come up with a way to mock the return type of useFetch + p2p_config: { + payment_methods_enabled: 1, + adverts_active_limit: 3, + adverts_archive_period: 3, + supported_currencies: ['usd'], + }, + }, + }, + }); + + const wrapper = ({ children }: TWrapper) => ( + + {children} + + ); + + const { result } = renderHook(() => useP2PConfig(), { wrapper }); + const p2p_config = result.current.data; + + expect(p2p_config?.payment_methods_enabled).toBe(1); + expect(p2p_config?.adverts_active_limit).toBe(3); + expect(p2p_config?.adverts_archive_period).toBe(3); + expect(p2p_config?.supported_currencies).toStrictEqual(['usd']); + }); +}); diff --git a/packages/hooks/src/__tests__/useP2PPaymentMethods.spec.tsx b/packages/hooks/src/__tests__/useP2PPaymentMethods.spec.tsx new file mode 100644 index 000000000000..54b15da7a6e9 --- /dev/null +++ b/packages/hooks/src/__tests__/useP2PPaymentMethods.spec.tsx @@ -0,0 +1,168 @@ +import * as React from 'react'; +import { useFetch } from '@deriv/api'; +import { StoreProvider, mockStore } from '@deriv/stores'; +import { renderHook } from '@testing-library/react-hooks'; +import useP2PPaymentMethods from '../useP2PPaymentMethods'; + +jest.mock('@deriv/api', () => ({ + ...jest.requireActual('@deriv/api'), + useFetch: jest.fn(), +})); + +const mockUseFetch = useFetch as jest.MockedFunction>; + +describe('useP2PPaymentMethods', () => { + test('should return undefined when p2p_payment_methods is not available', () => { + const mock = mockStore({}); + + // @ts-expect-error need to come up with a way to mock the return type of useFetch + mockUseFetch.mockReturnValue({ + data: {}, + }); + + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); + + const { result } = renderHook(() => useP2PPaymentMethods(), { wrapper }); + + expect(result.current.data).toBeUndefined(); + }); + + test('should return bank_transfer p2p payment methods with expected data', () => { + const mock = mockStore({}); + + // @ts-expect-error need to come up with a way to mock the return type of useFetch + mockUseFetch.mockReturnValue({ + data: { + p2p_payment_methods: { + bank_transfer: { + display_name: 'Bank Transfer', + fields: { + account: { + display_name: 'Account Number', + required: 1, + type: 'text', + }, + bank_code: { + display_name: 'SWIFT or IFSC code', + required: 0, + type: 'text', + }, + bank_name: { + display_name: 'Bank Name', + required: 1, + type: 'text', + }, + branch: { + display_name: 'Branch', + required: 0, + type: 'text', + }, + instructions: { + display_name: 'Instructions', + required: 0, + type: 'memo', + }, + }, + type: 'bank', + }, + }, + }, + }); + + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); + + const { result } = renderHook(() => useP2PPaymentMethods(), { wrapper }); + + expect(result.current.data?.[0]).toHaveProperty('display_name', 'Bank Transfer'); + expect(result.current.data?.[0]).toHaveProperty('fields'); + expect(result.current.data?.[0]).toHaveProperty('type', 'bank'); + expect(result.current.data?.[0]).toHaveProperty('icon', 'IcCashierBankTransfer'); + }); + + test('should return alipay (ewallet) p2p payment methods with expected data', () => { + const mock = mockStore({}); + + // @ts-expect-error need to come up with a way to mock the return type of useFetch + mockUseFetch.mockReturnValue({ + data: { + p2p_payment_methods: { + alipay: { + display_name: 'Alipay', + fields: { + account: { + display_name: 'Alipay ID', + required: 1, + type: 'text', + }, + instructions: { + display_name: 'Instructions', + required: 0, + type: 'memo', + }, + }, + type: 'ewallet', + }, + }, + }, + }); + + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); + + const { result } = renderHook(() => useP2PPaymentMethods(), { wrapper }); + + expect(result.current.data?.[0]).toHaveProperty('display_name', 'Alipay'); + expect(result.current.data?.[0]).toHaveProperty('fields'); + expect(result.current.data?.[0]).toHaveProperty('type', 'ewallet'); + expect(result.current.data?.[0]).toHaveProperty('icon', 'IcCashierEwallet'); + }); + + test('should return other p2p payment methods with expected data', () => { + const mock = mockStore({}); + + // @ts-expect-error need to come up with a way to mock the return type of useFetch + mockUseFetch.mockReturnValue({ + data: { + p2p_payment_methods: { + other: { + display_name: 'Other', + fields: { + account: { + display_name: 'Account ID / phone number / email', + required: 0, + type: 'text', + }, + instructions: { + display_name: 'Instructions', + required: 0, + type: 'memo', + }, + name: { + display_name: 'Payment method name', + required: 1, + type: 'text', + }, + }, + type: 'other', + }, + }, + }, + }); + + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); + + const { result } = renderHook(() => useP2PPaymentMethods(), { wrapper }); + + expect(result.current.data?.[0]).toHaveProperty('display_name', 'Other'); + expect(result.current.data?.[0]).toHaveProperty('fields'); + expect(result.current.data?.[0]).toHaveProperty('type', 'other'); + expect(result.current.data?.[0]).toHaveProperty('icon', 'IcCashierOther'); + }); +}); diff --git a/packages/hooks/src/index.ts b/packages/hooks/src/index.ts index 3c4a6a513e6e..4b22cbce43d4 100644 --- a/packages/hooks/src/index.ts +++ b/packages/hooks/src/index.ts @@ -34,7 +34,10 @@ export { default as useNeedFinancialAssessment } from './useNeedFinancialAssessm export { default as useNeedPOI } from './useNeedPOI'; export { default as useNeedTNC } from './useNeedTNC'; export { default as useOnrampVisible } from './useOnrampVisible'; +export { default as useP2PAdvertiserPaymentMethods } from './useP2PAdvertiserPaymentMethods'; +export { default as useP2PAdvertList } from './useP2PAdvertList'; export { default as useP2PNotificationCount } from './useP2PNotificationCount'; +export { default as useP2PPaymentMethods } from './useP2PPaymentMethods'; export { default as usePaymentAgentList } from './usePaymentAgentList'; export { default as usePaymentAgentTransferVisible } from './usePaymentAgentTransferVisible'; export { default as usePlatformAccounts } from './usePlatformAccounts'; @@ -43,7 +46,7 @@ export { default as usePlatformRealAccounts } from './usePlatformRealAccounts'; export { default as useRealSTPAccount } from './useRealSTPAccount'; export { default as useTotalAccountBalance } from './useTotalAccountBalance'; export { default as useVerifyEmail } from './useVerifyEmail'; -export { default as useP2PAdvertList } from './useP2PAdvertList'; export { useIsAccountStatusPresent } from './useIsAccountStatusPresent'; +export { default as useP2PConfig } from './useP2PConfig'; export { default as useIsClientHighRiskForMT5 } from './useIsClientHighRiskForMT5'; export { default as useCFDCanGetMoreMT5Accounts } from './useCFDCanGetMoreMT5Accounts'; diff --git a/packages/hooks/src/useCFDAllAccounts.ts b/packages/hooks/src/useCFDAllAccounts.ts index 0c14923eb571..109f4a202ba4 100644 --- a/packages/hooks/src/useCFDAllAccounts.ts +++ b/packages/hooks/src/useCFDAllAccounts.ts @@ -1,13 +1,9 @@ import { useStore } from '@deriv/stores'; -/** - * we can use this hook to get the CFD accounts for both Eu and Non-Eu regions. - * it gets dxtrade_accounts_list and mt5_login_list from store and merges them into one array - * and returns the array - */ +/** @deprecated Use `useMT5LoginList` for MT5 accounts and `useTradingPlatformAccounts` for Other CFD accounts from `@deriv/api` instead. */ const useCFDAllAccounts = () => { const { client } = useStore(); - const { dxtrade_accounts_list, mt5_login_list, derivez_accounts_list } = client; + const { dxtrade_accounts_list, mt5_login_list, derivez_accounts_list, ctrader_accounts_list } = client; let cfd_accounts: typeof mt5_login_list = []; if (Array.isArray(mt5_login_list)) { @@ -16,6 +12,9 @@ const useCFDAllAccounts = () => { if (Array.isArray(dxtrade_accounts_list)) { cfd_accounts = [...cfd_accounts, ...dxtrade_accounts_list]; } + if (Array.isArray(ctrader_accounts_list)) { + cfd_accounts = [...cfd_accounts, ...ctrader_accounts_list]; + } if (Array.isArray(derivez_accounts_list)) { cfd_accounts = [...cfd_accounts, ...derivez_accounts_list]; } diff --git a/packages/hooks/src/useHasP2PSupportedCurrencies.ts b/packages/hooks/src/useHasP2PSupportedCurrencies.ts index b7173542bb29..e87ff2216236 100644 --- a/packages/hooks/src/useHasP2PSupportedCurrencies.ts +++ b/packages/hooks/src/useHasP2PSupportedCurrencies.ts @@ -1,27 +1,19 @@ -import { useEffect } from 'react'; -import { useFetch, useInvalidateQuery } from '@deriv/api'; import { useStore } from '@deriv/stores'; +import useP2PConfig from './useP2PConfig'; const useHasP2PSupportedCurrencies = () => { const { client } = useStore(); - const { active_accounts, is_authorize, loginid } = client; - const invalidate = useInvalidateQuery(); - const { data, ...rest } = useFetch('website_status', { options: { enabled: is_authorize } }); + const { active_accounts } = client; + const { data, ...rest } = useP2PConfig(); const real_account_currencies_list = active_accounts .filter(account => !account.is_virtual) .map(account => account.currency?.toLowerCase()); const has_p2p_supported_currencies = Boolean( - data?.website_status?.p2p_config?.supported_currencies.some((currency: string) => - real_account_currencies_list.includes(currency) - ) + data?.supported_currencies.some((currency: string) => real_account_currencies_list.includes(currency)) ); - useEffect(() => { - invalidate('website_status'); - }, [invalidate, loginid]); - return { ...rest, data: has_p2p_supported_currencies, diff --git a/packages/hooks/src/useIsP2PEnabled.ts b/packages/hooks/src/useIsP2PEnabled.ts index 2c877c0d0a5a..1d820b31b0d3 100644 --- a/packages/hooks/src/useIsP2PEnabled.ts +++ b/packages/hooks/src/useIsP2PEnabled.ts @@ -1,26 +1,18 @@ -import { useEffect } from 'react'; -import { useFetch, useInvalidateQuery } from '@deriv/api'; import { useStore } from '@deriv/stores'; +import useP2PConfig from './useP2PConfig'; const useIsP2PEnabled = () => { const { client, traders_hub } = useStore(); // Todo: to replace it with useAuthorize hook - const { is_authorize, loginid, currency, is_virtual } = client; + const { currency, is_virtual } = client; const { is_low_risk_cr_eu_real } = traders_hub; - const invalidate = useInvalidateQuery(); - const { data, ...rest } = useFetch('website_status', { options: { enabled: is_authorize } }); + const { data, ...rest } = useP2PConfig(); - const is_p2p_supported_currency = Boolean( - data?.website_status?.p2p_config?.supported_currencies.includes(currency.toLocaleLowerCase()) - ); + const is_p2p_supported_currency = Boolean(data?.supported_currencies.includes(currency.toLocaleLowerCase())); const is_p2p_enabled = is_p2p_supported_currency && !is_virtual && !is_low_risk_cr_eu_real; // Todo: should replace with the next line instead once BE is fixed. - // const is_p2p_enabled = data?.p2p_config?.disabled === 0; - - useEffect(() => { - invalidate('website_status'); - }, [invalidate, loginid]); + // const is_p2p_enabled = data?.disabled === 0; return { ...rest, diff --git a/packages/hooks/src/useP2PAdvertiserPaymentMethods.ts b/packages/hooks/src/useP2PAdvertiserPaymentMethods.ts new file mode 100644 index 000000000000..48b66a61558b --- /dev/null +++ b/packages/hooks/src/useP2PAdvertiserPaymentMethods.ts @@ -0,0 +1,71 @@ +import { useCallback, useMemo } from 'react'; +import { useFetch, useInvalidateQuery, useRequest } from '@deriv/api'; +import { useStore } from '@deriv/stores'; + +type TPayloads = NonNullable< + NonNullable>['mutate']>[0]>['payload'] +>; +type TCreatePayload = NonNullable[0]; +type TUpdatePayload = NonNullable[0]; + +const type_to_icon_mapper = { + bank: 'IcCashierBankTransfer', + other: 'IcCashierOther', + ewallet: 'IcCashierEwallet', +}; + +/** A custom hook to fetch, create, update, and delete p2p advertiser payment methods */ +const useP2PAdvertiserPaymentMethods = () => { + const invalidate = useInvalidateQuery(); + const { client } = useStore(); + const { is_authorize } = client; + const { mutate, ...mutate_rest } = useRequest('p2p_advertiser_payment_methods', { + onSuccess: () => invalidate('p2p_advertiser_payment_methods'), + }); + const { data, ...rest } = useFetch('p2p_advertiser_payment_methods', { + options: { enabled: is_authorize }, + }); + + // Modify the response to add additional informations + const modified_data = useMemo(() => { + const p2p_advertiser_payment_methods = data?.p2p_advertiser_payment_methods; + + if (!p2p_advertiser_payment_methods) return undefined; + + return Object.keys(p2p_advertiser_payment_methods).map(key => { + const advertiser_payment_method = p2p_advertiser_payment_methods[key]; + + return { + ...advertiser_payment_method, + /** Icon for each payment method based on the type */ + icon: type_to_icon_mapper[advertiser_payment_method.type], + /** The id of payment method */ + id: key, + }; + }); + }, [data]); + + const create = useCallback((values: TCreatePayload) => mutate({ payload: { create: [{ ...values }] } }), [mutate]); + + const update = useCallback( + (id: string, values: TUpdatePayload) => mutate({ payload: { update: { [id]: { ...values } } } }), + [mutate] + ); + + const delete_payment_method = useCallback((id: number) => mutate({ payload: { delete: [id] } }), [mutate]); + + return { + /** The list of p2p advertiser payment methods */ + data: modified_data, + /** Sends a request to create new p2p advertiser payment method */ + create, + /** Sends a request to update existing p2p advertiser payment method */ + update, + /** Sends a request to delete existing p2p advertiser payment method */ + delete: delete_payment_method, + ...rest, + mutation: mutate_rest, + }; +}; + +export default useP2PAdvertiserPaymentMethods; diff --git a/packages/hooks/src/useP2PConfig.ts b/packages/hooks/src/useP2PConfig.ts new file mode 100644 index 000000000000..ce77dd1daf48 --- /dev/null +++ b/packages/hooks/src/useP2PConfig.ts @@ -0,0 +1,37 @@ +import React from 'react'; +import { useFetch, useInvalidateQuery } from '@deriv/api'; +import { useStore } from '@deriv/stores'; + +/** + * A custom hook to get the p2p_config information from `website_status` endpoint + */ +const useP2PConfig = () => { + const { client } = useStore(); + const { is_authorize, loginid } = client; + const { data, ...rest } = useFetch('website_status', { options: { enabled: is_authorize } }); + const invalidate = useInvalidateQuery(); + + // Add additional information to the p2p config data. + const modified_p2p_config = React.useMemo(() => { + const p2p_config = data?.website_status?.p2p_config; + + if (!p2p_config) return undefined; + return { + ...p2p_config, + /** Indicates if the payment methods feature is enabled. */ + is_payment_methods_enabled: Boolean(p2p_config?.payment_methods_enabled), + }; + }, [data?.website_status?.p2p_config]); + + React.useEffect(() => { + invalidate('website_status'); + }, [invalidate, loginid]); + + return { + /** The p2p config response. */ + data: modified_p2p_config, + ...rest, + }; +}; + +export default useP2PConfig; diff --git a/packages/hooks/src/useP2PPaymentMethods.ts b/packages/hooks/src/useP2PPaymentMethods.ts new file mode 100644 index 000000000000..3e0ebd5115fc --- /dev/null +++ b/packages/hooks/src/useP2PPaymentMethods.ts @@ -0,0 +1,46 @@ +import React from 'react'; +import { useFetch } from '@deriv/api'; +import { useStore } from '@deriv/stores'; + +const type_to_icon_mapper = { + bank: 'IcCashierBankTransfer', + other: 'IcCashierOther', + ewallet: 'IcCashierEwallet', +}; + +/** A custom hook that return the list of P2P available payment methods */ +const useP2PPaymentMethods = () => { + const { client } = useStore(); + const { is_authorize } = client; + + const { data, ...rest } = useFetch('p2p_payment_methods', { options: { enabled: is_authorize } }); + + // Modify the data to add additional information. + const modified_data = React.useMemo(() => { + const p2p_payment_methods = data?.p2p_payment_methods; + + if (!p2p_payment_methods) return undefined; + + return Object.keys(p2p_payment_methods).map(key => { + const payment_method = p2p_payment_methods[key]; + const fields = Object.keys(payment_method.fields).map(field_key => payment_method.fields[field_key]); + + return { + ...payment_method, + /** Payment method field definitions. */ + fields, + /** Icon for each payment method based on the type */ + icon: type_to_icon_mapper[payment_method.type], + /** Payment method id */ + id: key, + }; + }); + }, [data]); + + return { + data: modified_data, + ...rest, + }; +}; + +export default useP2PPaymentMethods; diff --git a/packages/hooks/src/useVerifyEmail.ts b/packages/hooks/src/useVerifyEmail.ts index 343edc3b018b..4d9961df3ac1 100644 --- a/packages/hooks/src/useVerifyEmail.ts +++ b/packages/hooks/src/useVerifyEmail.ts @@ -5,6 +5,9 @@ import useCountdown from './useCountdown'; const RESEND_COUNTDOWN = 60; +/** + * @deprecated Please use useVerifyEmail from @deriv/api instead + */ const useVerifyEmail = ( type: Parameters>['mutate']>[0]['payload']['type'] ) => { diff --git a/packages/integration/README.md b/packages/integration/README.md new file mode 100644 index 000000000000..ebc6ea15e86f --- /dev/null +++ b/packages/integration/README.md @@ -0,0 +1,168 @@ +# @deriv/integration + +This package contains the global files and mocked data for writing your integration tests in the deriv projects, you can easily install this package and use these data inside your tests. + +It uses [playwright](https://playwright.dev/) as its main framework and you can simply write test with it. +We strongly suggest you to read the [documentation](https://playwright.dev/docs/intro) of playwright once before you get started. + +## Run tests + +- You can run your tests with UI Mode for a better developer experience with time travel debugging, watch mode and more. + +``` +npx playwright test --ui +``` + +- Running all tests + +``` +npx playwright test +``` + +- Running a single test file + +``` +npx playwright test landing-page.spec.ts +``` + +- Run a set of test files + +``` +npx playwright test tests/todo-page/ tests/landing-page/ +``` + +- Run files that have landing or login in the file name + +``` +npx playwright test landing login +``` + +- Run the test with the title + +``` +npx playwright test -g "add a todo item" +``` + +- Running tests in headed mode + +``` +npx playwright test landing-page.spec.ts --headed +``` + +- Running tests on a specific project + +``` +npx playwright test landing-page.ts --project=chromium +``` + +## Debugging Tests + +- Debugging all tests: + +``` +npx playwright test --debug +``` + +- Debugging one test file: + +``` +npx playwright test example.spec.ts --debug +``` + +- Debugging a test from the line number where the test(.. is defined: + +``` +npx playwright test example.spec.ts:10 --debug +``` + +## Local Mock + +I've created an example `exampleMock.ts` file, that can be run with `ts-node ./exampleMock.ts` to start a web socket server on `localhost:10443` + +## Using mocks in tests + +Each test should setup the mocks they need from the pool of mocks provided. + +```js +import { test, expect } from '@playwright/test'; +import setupMocks from '../../utils/mocks/mocks'; +import mockGeneral from '../../mocks/general'; +import mockLoggedIn from '../../mocks/auth'; + +test('it shows the first name when logged in', async ({ page, baseURL }) => { + await setupMocks({ + baseURL, + page, + mocks: [mockGeneral, mockLoggedIn], + }); + await page.goto(`${baseURL}/app-store/traders-hub`); + + const firstName = await page.getByRole('button', { name: 'User Menu' }).first(); + expect(await firstName.inputValue()).toBeVisible(); +}); +``` + +## Writing your own mocks + +A mock is simply a function that is given a context object that contains the following keys: + +- req_id +- request +- response + +```js +export default function customTimeMock(context) { + if (context.request.time === 1) { + context.response = { + echo_req: context.request, + req_id: context.req_id, + msg_type: 'time', + time: (Date.now() / 1000).toFixed(0), + }; + } +} + +// Then add it to your mocks: +await setupMocks({ + baseURL, + page, + mocks: [mockGeneral, mockLoggedIn, customTimeMock], +}); +``` + +## Extending Mocks + +If you want to change how a base mock works, or "extend" a mock, you can use the middleware like approach. + +For example, let's say you want to add a `day` to the `time` endpoint. You can use the `mockTime` base mock, but add another mock to add the `day` key. + +```js +export default function mockTimeMonday(context) { + if (context.request.time === 1) { + context.response.day = 'Monday'; + } +} + +await setupMocks({ + baseURL, + page, + mocks: [mockTime, mockTimeMonday], +}); +``` + +**| Note:** The response will, by default, be undefined. Above, mockTime creates the first response, then mockTimeMonday get's the mutated response and adds a new key. For that reason, the order of mocks is important. + +## Dynamically changing + +If you have a test that needs to change a mock, in the middle of the run, you can use the `add` and `remove` functions returned from `createMockServer` + +```js +const mocks = await setupMocks({ + baseURL, + page, + mocks: [mockGeneral, mockLoggedIn], +}); + +mocks.remove(mockLoggedIn); +mocks.add(mockHelloWorld); +``` diff --git a/packages/integration/package.json b/packages/integration/package.json new file mode 100644 index 000000000000..4fa795bf6848 --- /dev/null +++ b/packages/integration/package.json @@ -0,0 +1,26 @@ +{ + "name": "@deriv/integration", + "version": "1.0.0", + "private": true, + "description": "This repo contains global files for our integration tests", + "author": "Niloofar Sadeghi ", + "main": "src/index.ts", + "eslintConfig": { + "rules": { + "testing-library/no-await-sync-query": "off", + "testing-library/prefer-screen-queries": "off" + } + }, + "devDependencies": { + "@playwright/test": "^1.37.1", + "@types/ws": "^8.5.5", + "selfsigned": "^2.1.1", + "typescript": "^4.6.3" + }, + "dependencies": { + "ws": "^8.13.0" + }, + "scripts": { + "test": "npx playwright test" + } +} diff --git a/packages/integration/src/exampleMock.ts b/packages/integration/src/exampleMock.ts new file mode 100644 index 000000000000..92f3a455c451 --- /dev/null +++ b/packages/integration/src/exampleMock.ts @@ -0,0 +1,12 @@ +import { createMockServer } from './utils/mocks/mocks'; +import general from './mocks/general'; +import auth from './mocks/auth'; +import residents_list from './mocks/location/residents_list'; +import states_list from './mocks/location/states_list'; + +async function main() { + await createMockServer([general, auth, residents_list, states_list], { port: 10443 }); + process.stdout.write('Listening on localhost:10443'); +} + +main(); diff --git a/packages/integration/src/index.ts b/packages/integration/src/index.ts new file mode 100644 index 000000000000..4bc7a773522d --- /dev/null +++ b/packages/integration/src/index.ts @@ -0,0 +1,24 @@ +export { default as mock_authorize } from './mocks/auth/authorize'; +export { default as mock_balance_all } from './mocks/auth/balance_all'; +export { default as mock_blalance_one } from './mocks/auth/balance_one'; +export { default as mock_get_account_status } from './mocks/auth/get_account_status'; +export { default as mock_get_financial_assessment } from './mocks/auth/get_financial_assessment'; +export { default as mock_get_limits } from './mocks/auth/get_limits'; +export { default as mock_get_self_exclusion } from './mocks/auth/get_self_exclusion'; +export { default as mock_get_settings } from './mocks/auth/get_settings'; +export { default as mock_landing_company } from './mocks/auth/landing_company'; +export { default as mock_mt5_login_list } from './mocks/auth/mt5_login_list'; +export { default as mock_paymentagent_list } from './mocks/auth/paymentagent_list'; +export { default as mock_platform_dxtrade } from './mocks/auth/platform_dxtrade'; +export { default as mock_platform_mt5 } from './mocks/auth/platform_mt5'; +export { default as mock_trading_platform_accounts } from './mocks/auth/trading_platform_accounts'; +export { default as mock_trading_platform_available_accounts } from './mocks/auth/trading_platform_available_accounts'; +export { default as mock_exchange_rates } from './mocks/general/exchange_rates'; +export { default as mock_payout_currencies } from './mocks/general/payout_currencies'; +export { default as mock_time } from './mocks/general/time'; +export { default as mock_website_status } from './mocks/general/website_status'; +export { default as mock_residents_list } from './mocks/location/residents_list'; +export { default as mock_states_list } from './mocks/location/states_list'; +export { default as mock_general } from './mocks/general'; +export { default as mock_loggedIn } from './mocks/auth'; +export { default as setupMocks } from './utils/mocks/mocks'; diff --git a/packages/integration/src/mocks/auth/authorize.ts b/packages/integration/src/mocks/auth/authorize.ts new file mode 100644 index 000000000000..d1cd9b441e6c --- /dev/null +++ b/packages/integration/src/mocks/auth/authorize.ts @@ -0,0 +1,70 @@ +import { Context } from 'Utils/mocks/mocks'; + +export default function mock_authorize(context: Context) { + if ('authorize' in context.request) { + context.response = { + authorize: { + account_list: [ + { + account_category: 'trading', + account_type: 'binary', + created_at: 1688638657, + currency: 'USD', + is_disabled: 0, + is_virtual: 0, + landing_company_name: 'svg', + linked_to: [], + loginid: 'CR5712715', + }, + { + account_category: 'trading', + account_type: 'binary', + created_at: 1688638635, + currency: 'BTC', + is_disabled: 0, + is_virtual: 0, + landing_company_name: 'svg', + linked_to: [], + loginid: 'CR5712710', + }, + { + account_category: 'trading', + account_type: 'binary', + created_at: 1688638579, + currency: 'USD', + is_disabled: 0, + is_virtual: 1, + landing_company_name: 'virtual', + linked_to: [], + loginid: 'VRTC8420051', + }, + ], + balance: 0, + country: 'th', + currency: 'USD', + email: 'jane.smith@example.com', + fullname: ' Jane Smith', + is_virtual: 0, + landing_company_fullname: 'Deriv (SVG) LLC', + landing_company_name: 'svg', + linked_to: [], + local_currencies: { + THB: { + fractional_digits: 2, + }, + }, + loginid: 'CR5712715', + preferred_language: 'EN', + scopes: ['read', 'trade', 'trading_information', 'payments', 'admin'], + upgradeable_landing_companies: ['svg'], + user_id: 10000001, + }, + echo_req: { + authorize: '', + req_id: context.req_id, + }, + req_id: context.req_id, + msg_type: 'authorize', + }; + } +} diff --git a/packages/integration/src/mocks/auth/balance_all.ts b/packages/integration/src/mocks/auth/balance_all.ts new file mode 100644 index 000000000000..7ba99714fbb8 --- /dev/null +++ b/packages/integration/src/mocks/auth/balance_all.ts @@ -0,0 +1,69 @@ +import { Context } from 'Utils/mocks/mocks'; + +export default function mock_balance_all(context: Context) { + if ('balance' in context.request && context.request.balance === 1 && context.request.account === 'all') { + context.response = { + balance: { + accounts: { + CR5712710: { + balance: 0, + converted_amount: 0, + currency: 'BTC', + demo_account: 0, + status: 1, + type: 'deriv', + }, + CR5712715: { + balance: 0, + converted_amount: 0, + currency: 'USD', + demo_account: 0, + status: 1, + type: 'deriv', + }, + VRTC8420051: { + balance: 10008.46, + converted_amount: 10008.46, + currency: 'USD', + demo_account: 1, + status: 1, + type: 'deriv', + }, + }, + balance: 0, + currency: 'USD', + id: 'd55abfb0-1f66-e9fc-b09d-9fc722186dee', + loginid: 'CR5712715', + total: { + deriv: { + amount: 0, + currency: 'USD', + }, + deriv_demo: { + amount: 10008.46, + currency: 'USD', + }, + mt5: { + amount: 0, + currency: 'USD', + }, + mt5_demo: { + amount: 0, + currency: 'USD', + }, + }, + }, + echo_req: { + account: 'all', + balance: 1, + req_id: context.req_id, + subscribe: 1, + }, + msg_type: 'balance', + req_id: context.req_id, + subscription: { + id: 'd55abfb0-1f66-e9fc-b09d-9fc722186dee', + }, + }; + } +} diff --git a/packages/integration/src/mocks/auth/balance_one.ts b/packages/integration/src/mocks/auth/balance_one.ts new file mode 100644 index 000000000000..0fec1007827d --- /dev/null +++ b/packages/integration/src/mocks/auth/balance_one.ts @@ -0,0 +1,25 @@ +import { Context } from 'Utils/mocks/mocks'; + +export default function mock_blalance_one(context: Context) { + if ('balance' in context.request && context.request.balance === 1 && context.request.account === 'CR5712715') { + context.response = { + balance: { + balance: 0, + currency: 'USD', + id: 'e5247f39-666e-9938-87d6-76415da2ffb4', + loginid: 'CR5712715', + }, + echo_req: { + account: 'CR5712715', + balance: 1, + req_id: context.req_id, + subscribe: 1, + }, + msg_type: 'balance', + req_id: context.req_id, + subscription: { + id: 'e5247f39-666e-9938-87d6-76415da2ffb4', + }, + }; + } +} diff --git a/packages/integration/src/mocks/auth/get_account_status.ts b/packages/integration/src/mocks/auth/get_account_status.ts new file mode 100644 index 000000000000..39dca9ad2f60 --- /dev/null +++ b/packages/integration/src/mocks/auth/get_account_status.ts @@ -0,0 +1,74 @@ +import { Context } from 'Utils/mocks/mocks'; + +export default function mock_get_account_status(context: Context) { + if ('get_account_status' in context.request && context.request.get_account_status === 1) { + context.response = { + echo_req: { + get_account_status: 1, + req_id: context.req_id, + }, + get_account_status: { + authentication: { + attempts: { + count: 0, + history: [], + latest: null, + }, + document: { + status: 'none', + }, + identity: { + services: { + idv: { + last_rejected: [], + reported_properties: {}, + status: 'none', + submissions_left: 3, + }, + manual: { + status: 'none', + }, + onfido: { + country_code: 'THA', + documents_supported: ['Driving Licence', 'National Identity Card', 'Passport'], + is_country_supported: 1, + last_rejected: [], + reported_properties: {}, + status: 'none', + submissions_left: 2, + }, + }, + status: 'none', + }, + income: { + status: 'none', + }, + needs_verification: [], + ownership: { + requests: [], + status: 'none', + }, + }, + currency_config: { + USD: { + is_deposit_suspended: 0, + is_withdrawal_suspended: 0, + }, + }, + p2p_status: 'active', + prompt_client_to_authenticate: 0, + risk_classification: 'low', + status: [ + 'allow_document_upload', + 'deposit_attempt', + 'dxtrade_password_not_set', + 'financial_information_not_complete', + 'mt5_password_not_set', + 'trading_experience_not_complete', + ], + }, + msg_type: 'get_account_status', + req_id: context.req_id, + }; + } +} diff --git a/packages/integration/src/mocks/auth/get_financial_assessment.ts b/packages/integration/src/mocks/auth/get_financial_assessment.ts new file mode 100644 index 000000000000..fd00989d8569 --- /dev/null +++ b/packages/integration/src/mocks/auth/get_financial_assessment.ts @@ -0,0 +1,15 @@ +import { Context } from 'Utils/mocks/mocks'; + +export default function mock_get_financial_assessment(context: Context) { + if ('get_financial_assessment' in context.request && context.request.get_financial_assessment === 1) { + context.response = { + echo_req: { + get_financial_assessment: 1, + req_id: context.req_id, + }, + get_financial_assessment: {}, + msg_type: 'get_financial_assessment', + req_id: context.req_id, + }; + } +} diff --git a/packages/integration/src/mocks/auth/get_limits.ts b/packages/integration/src/mocks/auth/get_limits.ts new file mode 100644 index 000000000000..9c7d42c676cc --- /dev/null +++ b/packages/integration/src/mocks/auth/get_limits.ts @@ -0,0 +1,137 @@ +import { Context } from 'Utils/mocks/mocks'; + +export default function mock_get_limits(context: Context) { + if ('get_limits' in context.request && context.request.get_limits === 1) { + context.response = { + echo_req: { + get_limits: 1, + req_id: context.req_id, + }, + get_limits: { + account_balance: null, + daily_cumulative_amount_transfers: { + dxtrade: { + allowed: 50000, + available: 50000, + }, + enabled: 0, + internal: { + allowed: 100000, + available: 100000, + }, + mt5: { + allowed: 200000, + available: 200000, + }, + }, + daily_transfers: { + ctrader: { + allowed: 10, + available: 10, + }, + derivez: { + allowed: 10, + available: 10, + }, + dxtrade: { + allowed: 10, + available: 10, + }, + internal: { + allowed: 10, + available: 10, + }, + mt5: { + allowed: 10, + available: 10, + }, + }, + lifetime_limit: 10000, + market_specific: { + commodities: [ + { + level: 'market', + name: 'Commodities', + payout_limit: 5000, + profile_name: 'moderate_risk', + turnover_limit: 50000, + }, + ], + cryptocurrency: [ + { + level: 'market', + name: 'Cryptocurrencies', + payout_limit: 100.0, + profile_name: 'extreme_risk', + turnover_limit: 1000.0, + }, + ], + forex: [ + { + level: 'submarket', + name: 'Minor Pairs', + payout_limit: 5000, + profile_name: 'moderate_risk', + turnover_limit: 50000, + }, + { + level: 'submarket', + name: 'Major Pairs', + payout_limit: 20000, + profile_name: 'medium_risk', + turnover_limit: 100000, + }, + { + level: 'market', + name: 'Forex', + payout_limit: 20000, + profile_name: 'medium_risk', + turnover_limit: 100000, + }, + ], + indices: [ + { + level: 'market', + name: 'Stock Indices', + payout_limit: 20000, + profile_name: 'medium_risk', + turnover_limit: 100000, + }, + ], + synthetic_index: [ + { + level: 'submarket', + name: 'Forex Basket', + payout_limit: 5000, + profile_name: 'moderate_risk', + turnover_limit: 50000, + }, + { + level: 'submarket', + name: 'Commodities Basket', + payout_limit: 5000, + profile_name: 'moderate_risk', + turnover_limit: 50000, + }, + { + level: 'market', + name: 'Derived', + payout_limit: 50000, + profile_name: 'low_risk', + turnover_limit: 500000, + }, + ], + }, + num_of_days: 30, + num_of_days_limit: 10000, + open_positions: 100, + payout: 50000, + remainder: 10000, + withdrawal_for_x_days_monetary: 0, + withdrawal_since_inception_monetary: 0, + }, + msg_type: 'get_limits', + req_id: context.req_id, + }; + } +} diff --git a/packages/integration/src/mocks/auth/get_self_exclusion.ts b/packages/integration/src/mocks/auth/get_self_exclusion.ts new file mode 100644 index 000000000000..98a736815a68 --- /dev/null +++ b/packages/integration/src/mocks/auth/get_self_exclusion.ts @@ -0,0 +1,15 @@ +import { Context } from 'Utils/mocks/mocks'; + +export default function mock_get_self_exclusion(context: Context) { + if ('get_self_exclusion' in context.request && context.request.get_self_exclusion === 1) { + context.response = { + echo_req: { + get_self_exclusion: 1, + req_id: context.req_id, + }, + get_self_exclusion: {}, + msg_type: 'get_self_exclusion', + req_id: context.req_id, + }; + } +} diff --git a/packages/integration/src/mocks/auth/get_settings.ts b/packages/integration/src/mocks/auth/get_settings.ts new file mode 100644 index 000000000000..73b21e7ef00f --- /dev/null +++ b/packages/integration/src/mocks/auth/get_settings.ts @@ -0,0 +1,50 @@ +import { Context } from 'Utils/mocks/mocks'; + +export default function mock_get_settings(context: Context) { + if ('get_settings' in context.request && context.request.get_settings === 1) { + context.response = { + echo_req: { + get_settings: 1, + req_id: context.req_id, + }, + get_settings: { + account_opening_reason: '', + address_city: 'test', + address_line_1: 'test', + address_line_2: '', + address_postcode: '', + address_state: '', + allow_copiers: 0, + citizen: 'th', + client_tnc_status: 'Version 4.2.0 2020-08-07', + country: 'Thailand', + country_code: 'th', + date_of_birth: 315532859, + dxtrade_user_exception: 0, + email: 'jane.smith@example.com', + email_consent: 1, + feature_flag: { + wallet: 0, + }, + first_name: 'Jane', + has_secret_answer: 1, + immutable_fields: ['residence'], + is_authenticated_payment_agent: 0, + last_name: 'Smith', + non_pep_declaration: 1, + phone: '+66111111111', + place_of_birth: null, + preferred_language: 'EN', + request_professional_status: 0, + residence: 'Thailand', + salutation: '', + tax_identification_number: null, + tax_residence: null, + trading_hub: 0, + user_hash: 'kYk8h4q605qCEBgOdiarruohRrFZemZwhgkHedMQEQ6EDhEgScm25lFIK42dSMK5', + }, + msg_type: 'get_settings', + req_id: context.req_id, + }; + } +} diff --git a/packages/integration/src/mocks/auth/index.ts b/packages/integration/src/mocks/auth/index.ts new file mode 100644 index 000000000000..7af6d438ce98 --- /dev/null +++ b/packages/integration/src/mocks/auth/index.ts @@ -0,0 +1,36 @@ +import mock_authorize from './authorize'; +import mock_balance_all from './balance_all'; +import mock_balance_one from './balance_one'; +import mock_get_account_status from './get_account_status'; +import mock_get_self_exclusion from './get_self_exclusion'; +import mock_get_settings from './get_settings'; +import mock_get_financial_assessment from './get_financial_assessment'; +import mock_mt5_login_list from './mt5_login_list'; +import mock_landing_company from './landing_company'; +import mock_get_limits from './get_limits'; +import mock_paymentagent_list from './paymentagent_list'; +import mock_trading_platform_available_accounts from './trading_platform_available_accounts'; +import mock_platform_mt5 from './platform_mt5'; +import mock_platform_dxtrade from './platform_dxtrade'; +import mock_trading_platform_accounts from './trading_platform_accounts'; +import { Context } from 'Utils/mocks/mocks'; + +const loggedIn = async (context: Context) => { + mock_authorize(context); + mock_balance_all(context); + mock_balance_one(context); + mock_get_account_status(context); + mock_get_self_exclusion(context); + mock_get_settings(context); + mock_get_financial_assessment(context); + mock_mt5_login_list(context); + mock_landing_company(context); + mock_get_limits(context); + mock_paymentagent_list(context); + mock_trading_platform_available_accounts(context); + mock_platform_mt5(context); + mock_platform_dxtrade(context); + mock_trading_platform_accounts(context); +}; + +export default loggedIn; diff --git a/packages/integration/src/mocks/auth/landing_company.ts b/packages/integration/src/mocks/auth/landing_company.ts new file mode 100644 index 000000000000..3469174d6da7 --- /dev/null +++ b/packages/integration/src/mocks/auth/landing_company.ts @@ -0,0 +1,1841 @@ +import { Context } from 'Utils/mocks/mocks'; + +export default function mock_landing_company(context: Context) { + if ('landing_company' in context.request) { + context.response = { + echo_req: { + landing_company: 'svg', + req_id: context.req_id, + }, + landing_company: { + all_company: 'svg', + config: {}, + ctrader: { + all: { + standard: 'svg', + }, + }, + derivez: { + all: { + standard: 'svg', + }, + }, + dxtrade_all_company: { + standard: { + address: null, + changeable_fields: { + only_before_auth: [ + 'salutation', + 'first_name', + 'last_name', + 'date_of_birth', + 'citizen', + 'account_opening_reason', + 'tax_residence', + 'tax_identification_number', + ], + personal_details_not_locked: [ + 'first_name', + 'last_name', + 'date_of_birth', + 'citizen', + 'place_of_birth', + ], + }, + country: 'Saint Vincent and the Grenadines', + currency_config: { + commodities: { + AUD: { + max_payout: 70000, + min_stake: 0.7, + }, + BTC: { + max_payout: 2, + min_stake: 0.00002, + }, + ETH: { + max_payout: 30, + min_stake: 0.0003, + }, + EUR: { + max_payout: 50000, + min_stake: 0.5, + }, + GBP: { + max_payout: 40000, + min_stake: 0.4, + }, + LTC: { + max_payout: 600, + min_stake: 0.006, + }, + USD: { + max_payout: 50000, + min_stake: 0.5, + }, + USDC: { + max_payout: 5000, + min_stake: 0.5, + }, + UST: { + max_payout: 5000, + min_stake: 0.5, + }, + eUSDT: { + max_payout: 5000, + min_stake: 0.5, + }, + tUSDT: { + max_payout: 5000, + min_stake: 0.5, + }, + }, + cryptocurrency: { + AUD: { + max_payout: 70000, + min_stake: 0.7, + }, + BTC: { + max_payout: 2, + min_stake: 0.00002, + }, + ETH: { + max_payout: 30, + min_stake: 0.0003, + }, + EUR: { + max_payout: 50000, + min_stake: 0.5, + }, + GBP: { + max_payout: 40000, + min_stake: 0.4, + }, + LTC: { + max_payout: 600, + min_stake: 0.006, + }, + USD: { + max_payout: 50000, + min_stake: 0.5, + }, + USDC: { + max_payout: 5000, + min_stake: 0.5, + }, + UST: { + max_payout: 5000, + min_stake: 0.5, + }, + eUSDT: { + max_payout: 5000, + min_stake: 0.5, + }, + tUSDT: { + max_payout: 5000, + min_stake: 0.5, + }, + }, + forex: { + AUD: { + max_payout: 70000, + min_stake: 0.7, + }, + BTC: { + max_payout: 2, + min_stake: 0.00002, + }, + ETH: { + max_payout: 30, + min_stake: 0.0003, + }, + EUR: { + max_payout: 50000, + min_stake: 0.5, + }, + GBP: { + max_payout: 40000, + min_stake: 0.4, + }, + LTC: { + max_payout: 600, + min_stake: 0.006, + }, + USD: { + max_payout: 50000, + min_stake: 0.5, + }, + USDC: { + max_payout: 5000, + min_stake: 0.5, + }, + UST: { + max_payout: 5000, + min_stake: 0.5, + }, + eUSDT: { + max_payout: 5000, + min_stake: 0.5, + }, + tUSDT: { + max_payout: 5000, + min_stake: 0.5, + }, + }, + indices: { + AUD: { + max_payout: 70000, + min_stake: 0.7, + }, + BTC: { + max_payout: 2, + min_stake: 0.00002, + }, + ETH: { + max_payout: 30, + min_stake: 0.0003, + }, + EUR: { + max_payout: 50000, + min_stake: 0.5, + }, + GBP: { + max_payout: 40000, + min_stake: 0.4, + }, + LTC: { + max_payout: 600, + min_stake: 0.006, + }, + USD: { + max_payout: 50000, + min_stake: 0.5, + }, + USDC: { + max_payout: 5000, + min_stake: 0.5, + }, + UST: { + max_payout: 5000, + min_stake: 0.5, + }, + eUSDT: { + max_payout: 5000, + min_stake: 0.5, + }, + tUSDT: { + max_payout: 5000, + min_stake: 0.5, + }, + }, + synthetic_index: { + AUD: { + max_payout: 70000, + min_stake: 0.5, + }, + BTC: { + max_payout: 2, + min_stake: 0.000013, + }, + ETH: { + max_payout: 30, + min_stake: 0.0002, + }, + EUR: { + max_payout: 50000, + min_stake: 0.3, + }, + GBP: { + max_payout: 40000, + min_stake: 0.3, + }, + LTC: { + max_payout: 600, + min_stake: 0.004, + }, + USD: { + max_payout: 50000, + min_stake: 0.35, + }, + USDC: { + max_payout: 5000, + min_stake: 0.3, + }, + UST: { + max_payout: 5000, + min_stake: 0.3, + }, + eUSDT: { + max_payout: 5000, + min_stake: 0.3, + }, + tUSDT: { + max_payout: 5000, + min_stake: 0.3, + }, + }, + }, + has_reality_check: 0, + legal_allowed_contract_categories: [ + 'asian', + 'callput', + 'callputequal', + 'callputspread', + 'digits', + 'endsinout', + 'highlowticks', + 'lookback', + 'multiplier', + 'reset', + 'runs', + 'staysinout', + 'touchnotouch', + ], + legal_allowed_currencies: [ + 'AUD', + 'BTC', + 'ETH', + 'EUR', + 'GBP', + 'LTC', + 'USD', + 'USDC', + 'UST', + 'eUSDT', + 'tUSDT', + ], + legal_allowed_markets: ['commodities', 'cryptocurrency', 'forex', 'indices', 'synthetic_index'], + legal_default_currency: 'USD', + name: 'Deriv (SVG) LLC', + requirements: { + signup: ['first_name', 'last_name', 'residence', 'date_of_birth'], + withdrawal: ['address_city', 'address_line_1'], + }, + shortcode: 'svg', + support_professional_client: 0, + tin_not_mandatory: 0, + }, + }, + financial_company: { + address: null, + changeable_fields: { + only_before_auth: [ + 'salutation', + 'first_name', + 'last_name', + 'date_of_birth', + 'citizen', + 'account_opening_reason', + 'tax_residence', + 'tax_identification_number', + ], + personal_details_not_locked: [ + 'first_name', + 'last_name', + 'date_of_birth', + 'citizen', + 'place_of_birth', + ], + }, + country: 'Saint Vincent and the Grenadines', + currency_config: { + commodities: { + AUD: { + max_payout: 70000, + min_stake: 0.7, + }, + BTC: { + max_payout: 2, + min_stake: 0.00002, + }, + ETH: { + max_payout: 30, + min_stake: 0.0003, + }, + EUR: { + max_payout: 50000, + min_stake: 0.5, + }, + GBP: { + max_payout: 40000, + min_stake: 0.4, + }, + LTC: { + max_payout: 600, + min_stake: 0.006, + }, + USD: { + max_payout: 50000, + min_stake: 0.5, + }, + USDC: { + max_payout: 5000, + min_stake: 0.5, + }, + UST: { + max_payout: 5000, + min_stake: 0.5, + }, + eUSDT: { + max_payout: 5000, + min_stake: 0.5, + }, + tUSDT: { + max_payout: 5000, + min_stake: 0.5, + }, + }, + cryptocurrency: { + AUD: { + max_payout: 70000, + min_stake: 0.7, + }, + BTC: { + max_payout: 2, + min_stake: 0.00002, + }, + ETH: { + max_payout: 30, + min_stake: 0.0003, + }, + EUR: { + max_payout: 50000, + min_stake: 0.5, + }, + GBP: { + max_payout: 40000, + min_stake: 0.4, + }, + LTC: { + max_payout: 600, + min_stake: 0.006, + }, + USD: { + max_payout: 50000, + min_stake: 0.5, + }, + USDC: { + max_payout: 5000, + min_stake: 0.5, + }, + UST: { + max_payout: 5000, + min_stake: 0.5, + }, + eUSDT: { + max_payout: 5000, + min_stake: 0.5, + }, + tUSDT: { + max_payout: 5000, + min_stake: 0.5, + }, + }, + forex: { + AUD: { + max_payout: 70000, + min_stake: 0.7, + }, + BTC: { + max_payout: 2, + min_stake: 0.00002, + }, + ETH: { + max_payout: 30, + min_stake: 0.0003, + }, + EUR: { + max_payout: 50000, + min_stake: 0.5, + }, + GBP: { + max_payout: 40000, + min_stake: 0.4, + }, + LTC: { + max_payout: 600, + min_stake: 0.006, + }, + USD: { + max_payout: 50000, + min_stake: 0.5, + }, + USDC: { + max_payout: 5000, + min_stake: 0.5, + }, + UST: { + max_payout: 5000, + min_stake: 0.5, + }, + eUSDT: { + max_payout: 5000, + min_stake: 0.5, + }, + tUSDT: { + max_payout: 5000, + min_stake: 0.5, + }, + }, + indices: { + AUD: { + max_payout: 70000, + min_stake: 0.7, + }, + BTC: { + max_payout: 2, + min_stake: 0.00002, + }, + ETH: { + max_payout: 30, + min_stake: 0.0003, + }, + EUR: { + max_payout: 50000, + min_stake: 0.5, + }, + GBP: { + max_payout: 40000, + min_stake: 0.4, + }, + LTC: { + max_payout: 600, + min_stake: 0.006, + }, + USD: { + max_payout: 50000, + min_stake: 0.5, + }, + USDC: { + max_payout: 5000, + min_stake: 0.5, + }, + UST: { + max_payout: 5000, + min_stake: 0.5, + }, + eUSDT: { + max_payout: 5000, + min_stake: 0.5, + }, + tUSDT: { + max_payout: 5000, + min_stake: 0.5, + }, + }, + synthetic_index: { + AUD: { + max_payout: 70000, + min_stake: 0.5, + }, + BTC: { + max_payout: 2, + min_stake: 0.000013, + }, + ETH: { + max_payout: 30, + min_stake: 0.0002, + }, + EUR: { + max_payout: 50000, + min_stake: 0.3, + }, + GBP: { + max_payout: 40000, + min_stake: 0.3, + }, + LTC: { + max_payout: 600, + min_stake: 0.004, + }, + USD: { + max_payout: 50000, + min_stake: 0.35, + }, + USDC: { + max_payout: 5000, + min_stake: 0.3, + }, + UST: { + max_payout: 5000, + min_stake: 0.3, + }, + eUSDT: { + max_payout: 5000, + min_stake: 0.3, + }, + tUSDT: { + max_payout: 5000, + min_stake: 0.3, + }, + }, + }, + has_reality_check: 0, + legal_allowed_contract_categories: [ + 'asian', + 'callput', + 'callputequal', + 'callputspread', + 'digits', + 'endsinout', + 'highlowticks', + 'lookback', + 'multiplier', + 'reset', + 'runs', + 'staysinout', + 'touchnotouch', + ], + legal_allowed_currencies: [ + 'AUD', + 'BTC', + 'ETH', + 'EUR', + 'GBP', + 'LTC', + 'USD', + 'USDC', + 'UST', + 'eUSDT', + 'tUSDT', + ], + legal_allowed_markets: ['commodities', 'cryptocurrency', 'forex', 'indices', 'synthetic_index'], + legal_default_currency: 'USD', + name: 'Deriv (SVG) LLC', + requirements: { + signup: ['first_name', 'last_name', 'residence', 'date_of_birth'], + withdrawal: ['address_city', 'address_line_1'], + }, + shortcode: 'svg', + support_professional_client: 0, + tin_not_mandatory: 0, + }, + gaming_company: { + address: null, + changeable_fields: { + only_before_auth: [ + 'salutation', + 'first_name', + 'last_name', + 'date_of_birth', + 'citizen', + 'account_opening_reason', + 'tax_residence', + 'tax_identification_number', + ], + personal_details_not_locked: [ + 'first_name', + 'last_name', + 'date_of_birth', + 'citizen', + 'place_of_birth', + ], + }, + country: 'Saint Vincent and the Grenadines', + currency_config: { + commodities: { + AUD: { + max_payout: 70000, + min_stake: 0.7, + }, + BTC: { + max_payout: 2, + min_stake: 0.00002, + }, + ETH: { + max_payout: 30, + min_stake: 0.0003, + }, + EUR: { + max_payout: 50000, + min_stake: 0.5, + }, + GBP: { + max_payout: 40000, + min_stake: 0.4, + }, + LTC: { + max_payout: 600, + min_stake: 0.006, + }, + USD: { + max_payout: 50000, + min_stake: 0.5, + }, + USDC: { + max_payout: 5000, + min_stake: 0.5, + }, + UST: { + max_payout: 5000, + min_stake: 0.5, + }, + eUSDT: { + max_payout: 5000, + min_stake: 0.5, + }, + tUSDT: { + max_payout: 5000, + min_stake: 0.5, + }, + }, + cryptocurrency: { + AUD: { + max_payout: 70000, + min_stake: 0.7, + }, + BTC: { + max_payout: 2, + min_stake: 0.00002, + }, + ETH: { + max_payout: 30, + min_stake: 0.0003, + }, + EUR: { + max_payout: 50000, + min_stake: 0.5, + }, + GBP: { + max_payout: 40000, + min_stake: 0.4, + }, + LTC: { + max_payout: 600, + min_stake: 0.006, + }, + USD: { + max_payout: 50000, + min_stake: 0.5, + }, + USDC: { + max_payout: 5000, + min_stake: 0.5, + }, + UST: { + max_payout: 5000, + min_stake: 0.5, + }, + eUSDT: { + max_payout: 5000, + min_stake: 0.5, + }, + tUSDT: { + max_payout: 5000, + min_stake: 0.5, + }, + }, + forex: { + AUD: { + max_payout: 70000, + min_stake: 0.7, + }, + BTC: { + max_payout: 2, + min_stake: 0.00002, + }, + ETH: { + max_payout: 30, + min_stake: 0.0003, + }, + EUR: { + max_payout: 50000, + min_stake: 0.5, + }, + GBP: { + max_payout: 40000, + min_stake: 0.4, + }, + LTC: { + max_payout: 600, + min_stake: 0.006, + }, + USD: { + max_payout: 50000, + min_stake: 0.5, + }, + USDC: { + max_payout: 5000, + min_stake: 0.5, + }, + UST: { + max_payout: 5000, + min_stake: 0.5, + }, + eUSDT: { + max_payout: 5000, + min_stake: 0.5, + }, + tUSDT: { + max_payout: 5000, + min_stake: 0.5, + }, + }, + indices: { + AUD: { + max_payout: 70000, + min_stake: 0.7, + }, + BTC: { + max_payout: 2, + min_stake: 0.00002, + }, + ETH: { + max_payout: 30, + min_stake: 0.0003, + }, + EUR: { + max_payout: 50000, + min_stake: 0.5, + }, + GBP: { + max_payout: 40000, + min_stake: 0.4, + }, + LTC: { + max_payout: 600, + min_stake: 0.006, + }, + USD: { + max_payout: 50000, + min_stake: 0.5, + }, + USDC: { + max_payout: 5000, + min_stake: 0.5, + }, + UST: { + max_payout: 5000, + min_stake: 0.5, + }, + eUSDT: { + max_payout: 5000, + min_stake: 0.5, + }, + tUSDT: { + max_payout: 5000, + min_stake: 0.5, + }, + }, + synthetic_index: { + AUD: { + max_payout: 70000, + min_stake: 0.5, + }, + BTC: { + max_payout: 2, + min_stake: 0.000013, + }, + ETH: { + max_payout: 30, + min_stake: 0.0002, + }, + EUR: { + max_payout: 50000, + min_stake: 0.3, + }, + GBP: { + max_payout: 40000, + min_stake: 0.3, + }, + LTC: { + max_payout: 600, + min_stake: 0.004, + }, + USD: { + max_payout: 50000, + min_stake: 0.35, + }, + USDC: { + max_payout: 5000, + min_stake: 0.3, + }, + UST: { + max_payout: 5000, + min_stake: 0.3, + }, + eUSDT: { + max_payout: 5000, + min_stake: 0.3, + }, + tUSDT: { + max_payout: 5000, + min_stake: 0.3, + }, + }, + }, + has_reality_check: 0, + legal_allowed_contract_categories: [ + 'asian', + 'callput', + 'callputequal', + 'callputspread', + 'digits', + 'endsinout', + 'highlowticks', + 'lookback', + 'multiplier', + 'reset', + 'runs', + 'staysinout', + 'touchnotouch', + ], + legal_allowed_currencies: [ + 'AUD', + 'BTC', + 'ETH', + 'EUR', + 'GBP', + 'LTC', + 'USD', + 'USDC', + 'UST', + 'eUSDT', + 'tUSDT', + ], + legal_allowed_markets: ['commodities', 'cryptocurrency', 'forex', 'indices', 'synthetic_index'], + legal_default_currency: 'USD', + name: 'Deriv (SVG) LLC', + requirements: { + signup: ['first_name', 'last_name', 'residence', 'date_of_birth'], + withdrawal: ['address_city', 'address_line_1'], + }, + shortcode: 'svg', + support_professional_client: 0, + tin_not_mandatory: 0, + }, + id: 'th', + minimum_age: 18, + mt_all_company: { + swap_free: { + address: null, + changeable_fields: { + only_before_auth: [ + 'salutation', + 'first_name', + 'last_name', + 'date_of_birth', + 'citizen', + 'account_opening_reason', + 'tax_residence', + 'tax_identification_number', + ], + personal_details_not_locked: [ + 'first_name', + 'last_name', + 'date_of_birth', + 'citizen', + 'place_of_birth', + ], + }, + country: 'Saint Vincent and the Grenadines', + currency_config: { + commodities: { + AUD: { + max_payout: 70000, + min_stake: 0.7, + }, + BTC: { + max_payout: 2, + min_stake: 0.00002, + }, + ETH: { + max_payout: 30, + min_stake: 0.0003, + }, + EUR: { + max_payout: 50000, + min_stake: 0.5, + }, + GBP: { + max_payout: 40000, + min_stake: 0.4, + }, + LTC: { + max_payout: 600, + min_stake: 0.006, + }, + USD: { + max_payout: 50000, + min_stake: 0.5, + }, + USDC: { + max_payout: 5000, + min_stake: 0.5, + }, + UST: { + max_payout: 5000, + min_stake: 0.5, + }, + eUSDT: { + max_payout: 5000, + min_stake: 0.5, + }, + tUSDT: { + max_payout: 5000, + min_stake: 0.5, + }, + }, + cryptocurrency: { + AUD: { + max_payout: 70000, + min_stake: 0.7, + }, + BTC: { + max_payout: 2, + min_stake: 0.00002, + }, + ETH: { + max_payout: 30, + min_stake: 0.0003, + }, + EUR: { + max_payout: 50000, + min_stake: 0.5, + }, + GBP: { + max_payout: 40000, + min_stake: 0.4, + }, + LTC: { + max_payout: 600, + min_stake: 0.006, + }, + USD: { + max_payout: 50000, + min_stake: 0.5, + }, + USDC: { + max_payout: 5000, + min_stake: 0.5, + }, + UST: { + max_payout: 5000, + min_stake: 0.5, + }, + eUSDT: { + max_payout: 5000, + min_stake: 0.5, + }, + tUSDT: { + max_payout: 5000, + min_stake: 0.5, + }, + }, + forex: { + AUD: { + max_payout: 70000, + min_stake: 0.7, + }, + BTC: { + max_payout: 2, + min_stake: 0.00002, + }, + ETH: { + max_payout: 30, + min_stake: 0.0003, + }, + EUR: { + max_payout: 50000, + min_stake: 0.5, + }, + GBP: { + max_payout: 40000, + min_stake: 0.4, + }, + LTC: { + max_payout: 600, + min_stake: 0.006, + }, + USD: { + max_payout: 50000, + min_stake: 0.5, + }, + USDC: { + max_payout: 5000, + min_stake: 0.5, + }, + UST: { + max_payout: 5000, + min_stake: 0.5, + }, + eUSDT: { + max_payout: 5000, + min_stake: 0.5, + }, + tUSDT: { + max_payout: 5000, + min_stake: 0.5, + }, + }, + indices: { + AUD: { + max_payout: 70000, + min_stake: 0.7, + }, + BTC: { + max_payout: 2, + min_stake: 0.00002, + }, + ETH: { + max_payout: 30, + min_stake: 0.0003, + }, + EUR: { + max_payout: 50000, + min_stake: 0.5, + }, + GBP: { + max_payout: 40000, + min_stake: 0.4, + }, + LTC: { + max_payout: 600, + min_stake: 0.006, + }, + USD: { + max_payout: 50000, + min_stake: 0.5, + }, + USDC: { + max_payout: 5000, + min_stake: 0.5, + }, + UST: { + max_payout: 5000, + min_stake: 0.5, + }, + eUSDT: { + max_payout: 5000, + min_stake: 0.5, + }, + tUSDT: { + max_payout: 5000, + min_stake: 0.5, + }, + }, + synthetic_index: { + AUD: { + max_payout: 70000, + min_stake: 0.5, + }, + BTC: { + max_payout: 2, + min_stake: 0.000013, + }, + ETH: { + max_payout: 30, + min_stake: 0.0002, + }, + EUR: { + max_payout: 50000, + min_stake: 0.3, + }, + GBP: { + max_payout: 40000, + min_stake: 0.3, + }, + LTC: { + max_payout: 600, + min_stake: 0.004, + }, + USD: { + max_payout: 50000, + min_stake: 0.35, + }, + USDC: { + max_payout: 5000, + min_stake: 0.3, + }, + UST: { + max_payout: 5000, + min_stake: 0.3, + }, + eUSDT: { + max_payout: 5000, + min_stake: 0.3, + }, + tUSDT: { + max_payout: 5000, + min_stake: 0.3, + }, + }, + }, + has_reality_check: 0, + legal_allowed_contract_categories: [ + 'asian', + 'callput', + 'callputequal', + 'callputspread', + 'digits', + 'endsinout', + 'highlowticks', + 'lookback', + 'multiplier', + 'reset', + 'runs', + 'staysinout', + 'touchnotouch', + ], + legal_allowed_currencies: [ + 'AUD', + 'BTC', + 'ETH', + 'EUR', + 'GBP', + 'LTC', + 'USD', + 'USDC', + 'UST', + 'eUSDT', + 'tUSDT', + ], + legal_allowed_markets: ['commodities', 'cryptocurrency', 'forex', 'indices', 'synthetic_index'], + legal_default_currency: 'USD', + name: 'Deriv (SVG) LLC', + requirements: { + signup: ['first_name', 'last_name', 'residence', 'date_of_birth'], + withdrawal: ['address_city', 'address_line_1'], + }, + shortcode: 'svg', + support_professional_client: 0, + tin_not_mandatory: 0, + }, + }, + mt_financial_company: { + financial: { + address: null, + changeable_fields: { + only_before_auth: [ + 'salutation', + 'first_name', + 'last_name', + 'date_of_birth', + 'citizen', + 'account_opening_reason', + 'tax_residence', + 'tax_identification_number', + ], + personal_details_not_locked: [ + 'first_name', + 'last_name', + 'date_of_birth', + 'citizen', + 'place_of_birth', + ], + }, + country: 'Saint Vincent and the Grenadines', + currency_config: { + commodities: { + AUD: { + max_payout: 70000, + min_stake: 0.7, + }, + BTC: { + max_payout: 2, + min_stake: 0.00002, + }, + ETH: { + max_payout: 30, + min_stake: 0.0003, + }, + EUR: { + max_payout: 50000, + min_stake: 0.5, + }, + GBP: { + max_payout: 40000, + min_stake: 0.4, + }, + LTC: { + max_payout: 600, + min_stake: 0.006, + }, + USD: { + max_payout: 50000, + min_stake: 0.5, + }, + USDC: { + max_payout: 5000, + min_stake: 0.5, + }, + UST: { + max_payout: 5000, + min_stake: 0.5, + }, + eUSDT: { + max_payout: 5000, + min_stake: 0.5, + }, + tUSDT: { + max_payout: 5000, + min_stake: 0.5, + }, + }, + cryptocurrency: { + AUD: { + max_payout: 70000, + min_stake: 0.7, + }, + BTC: { + max_payout: 2, + min_stake: 0.00002, + }, + ETH: { + max_payout: 30, + min_stake: 0.0003, + }, + EUR: { + max_payout: 50000, + min_stake: 0.5, + }, + GBP: { + max_payout: 40000, + min_stake: 0.4, + }, + LTC: { + max_payout: 600, + min_stake: 0.006, + }, + USD: { + max_payout: 50000, + min_stake: 0.5, + }, + USDC: { + max_payout: 5000, + min_stake: 0.5, + }, + UST: { + max_payout: 5000, + min_stake: 0.5, + }, + eUSDT: { + max_payout: 5000, + min_stake: 0.5, + }, + tUSDT: { + max_payout: 5000, + min_stake: 0.5, + }, + }, + forex: { + AUD: { + max_payout: 70000, + min_stake: 0.7, + }, + BTC: { + max_payout: 2, + min_stake: 0.00002, + }, + ETH: { + max_payout: 30, + min_stake: 0.0003, + }, + EUR: { + max_payout: 50000, + min_stake: 0.5, + }, + GBP: { + max_payout: 40000, + min_stake: 0.4, + }, + LTC: { + max_payout: 600, + min_stake: 0.006, + }, + USD: { + max_payout: 50000, + min_stake: 0.5, + }, + USDC: { + max_payout: 5000, + min_stake: 0.5, + }, + UST: { + max_payout: 5000, + min_stake: 0.5, + }, + eUSDT: { + max_payout: 5000, + min_stake: 0.5, + }, + tUSDT: { + max_payout: 5000, + min_stake: 0.5, + }, + }, + indices: { + AUD: { + max_payout: 70000, + min_stake: 0.7, + }, + BTC: { + max_payout: 2, + min_stake: 0.00002, + }, + ETH: { + max_payout: 30, + min_stake: 0.0003, + }, + EUR: { + max_payout: 50000, + min_stake: 0.5, + }, + GBP: { + max_payout: 40000, + min_stake: 0.4, + }, + LTC: { + max_payout: 600, + min_stake: 0.006, + }, + USD: { + max_payout: 50000, + min_stake: 0.5, + }, + USDC: { + max_payout: 5000, + min_stake: 0.5, + }, + UST: { + max_payout: 5000, + min_stake: 0.5, + }, + eUSDT: { + max_payout: 5000, + min_stake: 0.5, + }, + tUSDT: { + max_payout: 5000, + min_stake: 0.5, + }, + }, + synthetic_index: { + AUD: { + max_payout: 70000, + min_stake: 0.5, + }, + BTC: { + max_payout: 2, + min_stake: 0.000013, + }, + ETH: { + max_payout: 30, + min_stake: 0.0002, + }, + EUR: { + max_payout: 50000, + min_stake: 0.3, + }, + GBP: { + max_payout: 40000, + min_stake: 0.3, + }, + LTC: { + max_payout: 600, + min_stake: 0.004, + }, + USD: { + max_payout: 50000, + min_stake: 0.35, + }, + USDC: { + max_payout: 5000, + min_stake: 0.3, + }, + UST: { + max_payout: 5000, + min_stake: 0.3, + }, + eUSDT: { + max_payout: 5000, + min_stake: 0.3, + }, + tUSDT: { + max_payout: 5000, + min_stake: 0.3, + }, + }, + }, + has_reality_check: 0, + legal_allowed_contract_categories: [ + 'asian', + 'callput', + 'callputequal', + 'callputspread', + 'digits', + 'endsinout', + 'highlowticks', + 'lookback', + 'multiplier', + 'reset', + 'runs', + 'staysinout', + 'touchnotouch', + ], + legal_allowed_currencies: [ + 'AUD', + 'BTC', + 'ETH', + 'EUR', + 'GBP', + 'LTC', + 'USD', + 'USDC', + 'UST', + 'eUSDT', + 'tUSDT', + ], + legal_allowed_markets: ['commodities', 'cryptocurrency', 'forex', 'indices', 'synthetic_index'], + legal_default_currency: 'USD', + name: 'Deriv (SVG) LLC', + requirements: { + signup: ['first_name', 'last_name', 'residence', 'date_of_birth'], + withdrawal: ['address_city', 'address_line_1'], + }, + shortcode: 'svg', + support_professional_client: 0, + tin_not_mandatory: 0, + }, + financial_stp: { + address: [ + 'Labuan Times Square', + 'Jalan Merdeka', + '87000 Federal Territory of Labuan', + 'Malaysia', + ], + changeable_fields: {}, + country: 'Malaysia', + currency_config: { + forex: { + USD: { + max_payout: 50000, + min_stake: 0.5, + }, + }, + }, + has_reality_check: 0, + legal_allowed_contract_categories: ['callput'], + legal_allowed_currencies: ['USD'], + legal_allowed_markets: [], + legal_default_currency: 'USD', + name: 'Deriv (FX) Ltd', + requirements: { + after_first_deposit: { + financial_assessment: ['financial_information', 'trading_experience'], + }, + compliance: { + mt5: ['fully_authenticated', 'expiration_check'], + tax_information: ['tax_residence', 'tax_identification_number'], + }, + signup: ['phone', 'citizen', 'account_opening_reason'], + }, + shortcode: 'labuan', + support_professional_client: 0, + tin_not_mandatory: 0, + }, + }, + mt_gaming_company: { + financial: { + address: null, + changeable_fields: { + only_before_auth: [ + 'salutation', + 'first_name', + 'last_name', + 'date_of_birth', + 'citizen', + 'account_opening_reason', + 'tax_residence', + 'tax_identification_number', + ], + personal_details_not_locked: [ + 'first_name', + 'last_name', + 'date_of_birth', + 'citizen', + 'place_of_birth', + ], + }, + country: 'Saint Vincent and the Grenadines', + currency_config: { + commodities: { + AUD: { + max_payout: 70000, + min_stake: 0.7, + }, + BTC: { + max_payout: 2, + min_stake: 0.00002, + }, + ETH: { + max_payout: 30, + min_stake: 0.0003, + }, + EUR: { + max_payout: 50000, + min_stake: 0.5, + }, + GBP: { + max_payout: 40000, + min_stake: 0.4, + }, + LTC: { + max_payout: 600, + min_stake: 0.006, + }, + USD: { + max_payout: 50000, + min_stake: 0.5, + }, + USDC: { + max_payout: 5000, + min_stake: 0.5, + }, + UST: { + max_payout: 5000, + min_stake: 0.5, + }, + eUSDT: { + max_payout: 5000, + min_stake: 0.5, + }, + tUSDT: { + max_payout: 5000, + min_stake: 0.5, + }, + }, + cryptocurrency: { + AUD: { + max_payout: 70000, + min_stake: 0.7, + }, + BTC: { + max_payout: 2, + min_stake: 0.00002, + }, + ETH: { + max_payout: 30, + min_stake: 0.0003, + }, + EUR: { + max_payout: 50000, + min_stake: 0.5, + }, + GBP: { + max_payout: 40000, + min_stake: 0.4, + }, + LTC: { + max_payout: 600, + min_stake: 0.006, + }, + USD: { + max_payout: 50000, + min_stake: 0.5, + }, + USDC: { + max_payout: 5000, + min_stake: 0.5, + }, + UST: { + max_payout: 5000, + min_stake: 0.5, + }, + eUSDT: { + max_payout: 5000, + min_stake: 0.5, + }, + tUSDT: { + max_payout: 5000, + min_stake: 0.5, + }, + }, + forex: { + AUD: { + max_payout: 70000, + min_stake: 0.7, + }, + BTC: { + max_payout: 2, + min_stake: 0.00002, + }, + ETH: { + max_payout: 30, + min_stake: 0.0003, + }, + EUR: { + max_payout: 50000, + min_stake: 0.5, + }, + GBP: { + max_payout: 40000, + min_stake: 0.4, + }, + LTC: { + max_payout: 600, + min_stake: 0.006, + }, + USD: { + max_payout: 50000, + min_stake: 0.5, + }, + USDC: { + max_payout: 5000, + min_stake: 0.5, + }, + UST: { + max_payout: 5000, + min_stake: 0.5, + }, + eUSDT: { + max_payout: 5000, + min_stake: 0.5, + }, + tUSDT: { + max_payout: 5000, + min_stake: 0.5, + }, + }, + indices: { + AUD: { + max_payout: 70000, + min_stake: 0.7, + }, + BTC: { + max_payout: 2, + min_stake: 0.00002, + }, + ETH: { + max_payout: 30, + min_stake: 0.0003, + }, + EUR: { + max_payout: 50000, + min_stake: 0.5, + }, + GBP: { + max_payout: 40000, + min_stake: 0.4, + }, + LTC: { + max_payout: 600, + min_stake: 0.006, + }, + USD: { + max_payout: 50000, + min_stake: 0.5, + }, + USDC: { + max_payout: 5000, + min_stake: 0.5, + }, + UST: { + max_payout: 5000, + min_stake: 0.5, + }, + eUSDT: { + max_payout: 5000, + min_stake: 0.5, + }, + tUSDT: { + max_payout: 5000, + min_stake: 0.5, + }, + }, + synthetic_index: { + AUD: { + max_payout: 70000, + min_stake: 0.5, + }, + BTC: { + max_payout: 2, + min_stake: 0.000013, + }, + ETH: { + max_payout: 30, + min_stake: 0.0002, + }, + EUR: { + max_payout: 50000, + min_stake: 0.3, + }, + GBP: { + max_payout: 40000, + min_stake: 0.3, + }, + LTC: { + max_payout: 600, + min_stake: 0.004, + }, + USD: { + max_payout: 50000, + min_stake: 0.35, + }, + USDC: { + max_payout: 5000, + min_stake: 0.3, + }, + UST: { + max_payout: 5000, + min_stake: 0.3, + }, + eUSDT: { + max_payout: 5000, + min_stake: 0.3, + }, + tUSDT: { + max_payout: 5000, + min_stake: 0.3, + }, + }, + }, + has_reality_check: 0, + legal_allowed_contract_categories: [ + 'asian', + 'callput', + 'callputequal', + 'callputspread', + 'digits', + 'endsinout', + 'highlowticks', + 'lookback', + 'multiplier', + 'reset', + 'runs', + 'staysinout', + 'touchnotouch', + ], + legal_allowed_currencies: [ + 'AUD', + 'BTC', + 'ETH', + 'EUR', + 'GBP', + 'LTC', + 'USD', + 'USDC', + 'UST', + 'eUSDT', + 'tUSDT', + ], + legal_allowed_markets: ['commodities', 'cryptocurrency', 'forex', 'indices', 'synthetic_index'], + legal_default_currency: 'USD', + name: 'Deriv (SVG) LLC', + requirements: { + signup: ['first_name', 'last_name', 'residence', 'date_of_birth'], + withdrawal: ['address_city', 'address_line_1'], + }, + shortcode: 'svg', + support_professional_client: 0, + tin_not_mandatory: 0, + }, + }, + name: 'Thailand', + virtual_company: 'virtual', + }, + msg_type: 'landing_company', + req_id: context.req_id, + }; + } +} diff --git a/packages/integration/src/mocks/auth/mt5_login_list.ts b/packages/integration/src/mocks/auth/mt5_login_list.ts new file mode 100644 index 000000000000..39fa2731bf78 --- /dev/null +++ b/packages/integration/src/mocks/auth/mt5_login_list.ts @@ -0,0 +1,15 @@ +import { Context } from 'Utils/mocks/mocks'; + +export default function mock_mt5_login_list(context: Context) { + if ('mt5_login_list' in context.request && context.request.mt5_login_list === 1) { + context.response = { + echo_req: { + mt5_login_list: 1, + req_id: context.req_id, + }, + msg_type: 'mt5_login_list', + mt5_login_list: [], + req_id: context.req_id, + }; + } +} diff --git a/packages/integration/src/mocks/auth/paymentagent_list.ts b/packages/integration/src/mocks/auth/paymentagent_list.ts new file mode 100644 index 000000000000..bf0de3455682 --- /dev/null +++ b/packages/integration/src/mocks/auth/paymentagent_list.ts @@ -0,0 +1,156 @@ +import { Context } from 'Utils/mocks/mocks'; + +export default function mock_paymentagent_list(context: Context) { + if ( + 'paymentagent_list' in context.request && + context.request.paymentagent_list === 'th' && + context.request.currency === 'USD' + ) { + context.response = { + echo_req: { + currency: 'USD', + paymentagent_list: 'th', + req_id: context.req_id, + }, + msg_type: 'paymentagent_list', + paymentagent_list: { + available_countries: [ + ['af', 'Afghanistan'], + ['ao', 'Angola'], + ['ar', 'Argentina'], + ['au', 'Australia'], + ['bd', 'Bangladesh'], + ['bf', 'Burkina Faso'], + ['bi', 'Burundi'], + ['bj', 'Benin'], + ['bo', 'Bolivia'], + ['br', 'Brazil'], + ['bw', 'Botswana'], + ['cd', 'Congo - Kinshasa'], + ['cg', 'Congo - Brazzaville'], + ['ci', "Cote d'Ivoire"], + ['cl', 'Chile'], + ['cm', 'Cameroon'], + ['cn', 'China'], + ['co', 'Colombia'], + ['cu', 'Cuba'], + ['dj', 'Djibouti'], + ['do', 'Dominican Republic'], + ['dz', 'Algeria'], + ['ec', 'Ecuador'], + ['eg', 'Egypt'], + ['et', 'Ethiopia'], + ['ga', 'Gabon'], + ['ge', 'Georgia'], + ['gh', 'Ghana'], + ['gn', 'Guinea'], + ['gw', 'Guinea-Bissau'], + ['id', 'Indonesia'], + ['in', 'India'], + ['jp', 'Japan'], + ['ke', 'Kenya'], + ['kh', 'Cambodia'], + ['kr', 'South Korea'], + ['kw', 'Kuwait'], + ['la', 'Laos'], + ['lb', 'Lebanon'], + ['lk', 'Sri Lanka'], + ['ls', 'Lesotho'], + ['ly', 'Libya'], + ['ma', 'Morocco'], + ['mg', 'Madagascar'], + ['ml', 'Mali'], + ['mn', 'Mongolia'], + ['mv', 'Maldives'], + ['mw', 'Malawi'], + ['mz', 'Mozambique'], + ['na', 'Namibia'], + ['ne', 'Niger'], + ['ng', 'Nigeria'], + ['om', 'Oman'], + ['pe', 'Peru'], + ['ph', 'Philippines'], + ['pk', 'Pakistan'], + ['qa', 'Qatar'], + ['ru', 'Russia'], + ['sa', 'Saudi Arabia'], + ['sd', 'Sudan'], + ['sl', 'Sierra Leone'], + ['sn', 'Senegal'], + ['so', 'Somalia'], + ['ss', 'South Sudan'], + ['sz', 'Swaziland'], + ['tg', 'Togo'], + ['th', 'Thailand'], + ['tr', 'Turkey'], + ['tz', 'Tanzania'], + ['ug', 'Uganda'], + ['uy', 'Uruguay'], + ['vn', 'Vietnam'], + ['ws', 'Samoa'], + ['za', 'South Africa'], + ['zm', 'Zambia'], + ['zw', 'Zimbabwe'], + ], + list: [ + { + currencies: 'USD', + deposit_commission: '0', + email: 'teclasarojani@gmail.com', + further_information: + 'ආයුබෝවන් ඔබට අවශ්‍ය Binary / Deriv $ මිලට ගැනීමට අප සමග සමිබන්ධ වෙන්න. Bank Transfer( cash ), USDT Exchange , E-wallet Exchange සමග ඉතා කඩිනම් සුහදශීලී හා ලාබදායි ලෙස ඔබට අපගෙන් ලබාගත හැක. Binary/ Deriv Dollars,USDT, Crypto மாற்றம் அல்லது வாங்குதல் மற்றும் விற்பனை நோக்கங்களுக்காக என்னை தொடர்பு கொள்ளவும். குறைந்த விகிதங்கள் மற்றும் வேகமான சேவைகள் எங்களிடம் பெற்றுக்கொள்ளலாம். You are warmly welcome to my premium payment agent group. Whatsapp following Number for more details.', + max_withdrawal: '2000', + min_withdrawal: '10', + name: 'Chamara Madusanka Dharmasena', + paymentagent_loginid: 'CR2154875', + phone_numbers: [ + { + phone_number: '+94761540855', + }, + ], + summary: '', + supported_payment_methods: [ + { + payment_method: 'Bank transfer', + }, + { + payment_method: 'Boc Peoples Bank', + }, + { + payment_method: 'BTC', + }, + { + payment_method: 'Crypto Payment', + }, + { + payment_method: 'E-wallets', + }, + { + payment_method: 'HNB', + }, + { + payment_method: 'LOLC', + }, + { + payment_method: 'Sampath Bank', + }, + { + payment_method: 'USDT', + }, + ], + urls: [ + { + url: 'https://t.me/+b8-HyBzP09U3NDhl', + }, + { + url: 'https://www.facebook.com/m8az2z?mibextid=LQQJ4d', + }, + ], + withdrawal_commission: '0', + }, + ], + }, + req_id: context.req_id, + }; + } +} diff --git a/packages/integration/src/mocks/auth/platform_dxtrade.ts b/packages/integration/src/mocks/auth/platform_dxtrade.ts new file mode 100644 index 000000000000..c9602fb6d244 --- /dev/null +++ b/packages/integration/src/mocks/auth/platform_dxtrade.ts @@ -0,0 +1,27 @@ +import { Context } from 'Utils/mocks/mocks'; + +export default function mock_platform_dxtrade(context: Context) { + if ('platform' in context.request && context.request.platform === 'dxtrade') { + context.response = { + echo_req: { + platform: 'dxtrade', + req_id: context.req_id, + trading_servers: 1, + }, + msg_type: 'trading_servers', + req_id: context.req_id, + trading_servers: [ + { + account_type: 'real', + disabled: 0, + supported_accounts: ['all'], + }, + { + account_type: 'demo', + disabled: 0, + supported_accounts: ['all'], + }, + ], + }; + } +} diff --git a/packages/integration/src/mocks/auth/platform_mt5.ts b/packages/integration/src/mocks/auth/platform_mt5.ts new file mode 100644 index 000000000000..f0b3dc418df9 --- /dev/null +++ b/packages/integration/src/mocks/auth/platform_mt5.ts @@ -0,0 +1,32 @@ +import { Context } from 'Utils/mocks/mocks'; + +export default function mock_platform_mt5(context: Context) { + if ('platform' in context.request && context.request.platform === 'mt5') { + context.response = { + echo_req: { + platform: 'mt5', + req_id: context.req_id, + trading_servers: 1, + }, + msg_type: 'trading_servers', + req_id: context.req_id, + trading_servers: [ + { + account_type: 'real', + disabled: 0, + environment: 'Deriv-Server', + geolocation: { + group: 'asia_synthetic', + location: 'Singapore', + region: 'Asia', + sequence: 1, + }, + id: 'p01_ts03', + market_type: 'synthetic', + recommended: 1, + supported_accounts: ['gaming'], + }, + ], + }; + } +} diff --git a/packages/integration/src/mocks/auth/trading_platform_accounts.ts b/packages/integration/src/mocks/auth/trading_platform_accounts.ts new file mode 100644 index 000000000000..f01b0d93f2b2 --- /dev/null +++ b/packages/integration/src/mocks/auth/trading_platform_accounts.ts @@ -0,0 +1,16 @@ +import { Context } from 'Utils/mocks/mocks'; + +export default function mock_trading_platform_accounts(context: Context) { + if ('trading_platform_accounts' in context.request && context.request.trading_platform_accounts === 1) { + context.response = { + echo_req: { + platform: 'derivez', + req_id: context.req_id, + trading_platform_accounts: 1, + }, + msg_type: 'transfer_between_accounts', + req_id: context.req_id, + trading_platform_accounts: [], + }; + } +} diff --git a/packages/integration/src/mocks/auth/trading_platform_available_accounts.ts b/packages/integration/src/mocks/auth/trading_platform_available_accounts.ts new file mode 100644 index 000000000000..6719784d6d84 --- /dev/null +++ b/packages/integration/src/mocks/auth/trading_platform_available_accounts.ts @@ -0,0 +1,150 @@ +import { Context } from 'Utils/mocks/mocks'; + +export default function mock_trading_platform_available_accounts(context: Context) { + if ( + 'trading_platform_available_accounts' in context.request && + context.request.trading_platform_available_accounts === 1 + ) { + context.response = { + echo_req: { + platform: 'mt5', + req_id: context.req_id, + trading_platform_available_accounts: 1, + }, + msg_type: 'transfer_between_accounts', + req_id: context.req_id, + trading_platform_available_accounts: [ + { + linkable_landing_companies: ['svg'], + market_type: 'all', + name: 'Deriv (SVG) LLC', + requirements: { + signup: ['first_name', 'last_name', 'residence', 'date_of_birth'], + withdrawal: ['address_city', 'address_line_1'], + }, + shortcode: 'svg', + sub_account_type: 'swap_free', + }, + { + linkable_landing_companies: ['svg'], + market_type: 'financial', + name: 'Deriv (SVG) LLC', + requirements: { + signup: ['first_name', 'last_name', 'residence', 'date_of_birth'], + withdrawal: ['address_city', 'address_line_1'], + }, + shortcode: 'svg', + sub_account_type: 'standard', + }, + { + linkable_landing_companies: ['svg'], + market_type: 'financial', + name: 'Deriv (BVI) Ltd', + requirements: { + after_first_deposit: { + financial_assessment: ['financial_information', 'trading_experience'], + }, + compliance: { + mt5: ['fully_authenticated', 'expiration_check'], + tax_information: ['tax_residence', 'tax_identification_number'], + }, + signup: ['phone', 'citizen', 'account_opening_reason'], + }, + shortcode: 'bvi', + sub_account_type: 'standard', + }, + { + linkable_landing_companies: ['svg'], + market_type: 'financial', + name: 'Deriv (V) Ltd', + requirements: { + after_first_deposit: { + financial_assessment: ['financial_information'], + }, + compliance: { + mt5: ['fully_authenticated', 'expiration_check'], + tax_information: ['tax_residence', 'tax_identification_number'], + }, + signup: [ + 'citizen', + 'place_of_birth', + 'tax_residence', + 'tax_identification_number', + 'account_opening_reason', + ], + }, + shortcode: 'vanuatu', + sub_account_type: 'standard', + }, + { + linkable_landing_companies: ['svg'], + market_type: 'financial', + name: 'Deriv (FX) Ltd', + requirements: { + after_first_deposit: { + financial_assessment: ['financial_information', 'trading_experience'], + }, + compliance: { + mt5: ['fully_authenticated', 'expiration_check'], + tax_information: ['tax_residence', 'tax_identification_number'], + }, + signup: ['phone', 'citizen', 'account_opening_reason'], + }, + shortcode: 'labuan', + sub_account_type: 'stp', + }, + { + linkable_landing_companies: ['svg'], + market_type: 'gaming', + name: 'Deriv (SVG) LLC', + requirements: { + signup: ['first_name', 'last_name', 'residence', 'date_of_birth'], + withdrawal: ['address_city', 'address_line_1'], + }, + shortcode: 'svg', + sub_account_type: 'standard', + }, + { + linkable_landing_companies: ['svg'], + market_type: 'gaming', + name: 'Deriv (BVI) Ltd', + requirements: { + after_first_deposit: { + financial_assessment: ['financial_information', 'trading_experience'], + }, + compliance: { + mt5: ['fully_authenticated', 'expiration_check'], + tax_information: ['tax_residence', 'tax_identification_number'], + }, + signup: ['phone', 'citizen', 'account_opening_reason'], + }, + shortcode: 'bvi', + sub_account_type: 'standard', + }, + { + linkable_landing_companies: ['svg'], + market_type: 'gaming', + name: 'Deriv (V) Ltd', + requirements: { + after_first_deposit: { + financial_assessment: ['financial_information'], + }, + compliance: { + mt5: ['fully_authenticated', 'expiration_check'], + tax_information: ['tax_residence', 'tax_identification_number'], + }, + signup: [ + 'citizen', + 'place_of_birth', + 'tax_residence', + 'tax_identification_number', + 'account_opening_reason', + ], + }, + shortcode: 'vanuatu', + sub_account_type: 'standard', + }, + ], + }; + } +} diff --git a/packages/integration/src/mocks/general/exchange_rates.ts b/packages/integration/src/mocks/general/exchange_rates.ts new file mode 100644 index 000000000000..bc2b8ec6c314 --- /dev/null +++ b/packages/integration/src/mocks/general/exchange_rates.ts @@ -0,0 +1,160 @@ +import { Context } from 'Utils/mocks/mocks'; + +export default function mock_exchange_rates(context: Context) { + if ('exchange_rates' in context.request && context.request.exchange_rates === 1) { + context.response = { + echo_req: { + req_id: context.req_id, + exchange_rates: 1, + }, + exchange_rates: { + base_currency: 'USD', + date: parseInt((Date.now() / 1000).toFixed(0)), + rates: { + AED: 3.6735, + AFN: 89.0052, + ALL: 93.575, + AMD: 386.25, + AOA: 509.3611, + ARS: 113.9705, + AUD: 1.48029724368653, + AWG: 1.79, + BAM: 1.7659, + BBD: 2, + BDT: 108.4525, + BHD: 0.377, + BMD: 1, + BND: 1.3266, + BRL: 4.6221, + BSD: 1, + BTC: 3.41233456575483e-5, + BTN: 81.9938, + BUSD: 1.000100010001, + BWP: 13.587, + BZD: 2, + CAD: 1.32249, + CHF: 0.86227, + CLP: 814.6, + CNY: 7.142, + COP: 3759.31, + CRC: 541.275, + CUP: 24, + CVE: 100.0963, + DAI: 1, + DJF: 178.0389, + DKK: 6.7279, + DOP: 55.916, + DZD: 134.6946, + ECS: 25000, + EGP: 30.8974, + ERN: 15, + ETB: 54.8732, + ETH: 0.000537650306863913, + EUR: 0.902869318694812, + EURS: 0.85247858147564, + FJD: 2.2183, + FKP: 0.773275595422208, + GBP: 0.773305494335537, + GEL: 2.568, + GHC: 113284.5, + GIP: 0.773275595422208, + GMD: 62.4125, + GNF: 8593.675, + GTQ: 7.8508, + HKD: 7.8015, + HNL: 24.6193, + HTG: 138.119, + IDK: 15.0325, + IDR: 15032.5, + ILS: 3.6939, + INR: 81.991, + IQD: 1460, + ISK: 131.375, + JMD: 153.8087, + JOD: 0.7091, + JPY: 140.39, + KES: 135.43, + KMF: 444.1867, + KRW: 1274.35, + KWD: 0.3078, + KYD: 0.82, + KZT: 444.365, + LAK: 18882.385, + LKR: 328.89, + LRD: 154.7192, + LSL: 17.6615, + LTC: 0.0111569786901707, + LYD: 4.8042, + MAD: 9.6871, + MDL: 17.763, + MGA: 4328.755, + MKD: 55.5267, + MMK: 2010.105, + MNT: 3442.345, + MOP: 8.0356, + MUR: 45.9622, + MXN: 16.8675, + MZM: 63920.4551, + NAD: 17.6615, + NGN: 775.755, + NIO: 36.5748, + NOK: 10.11731, + NPR: 131.1751, + NZD: 1.61064312980173, + OMR: 0.3854, + PAB: 1, + PAX: 1.0005623160216, + PEN: 3.7128, + PHP: 54.621, + PKR: 285.3395, + PLN: 3.9981, + QAR: 3.6466, + RUB: 81.2679, + RWF: 1077.2937, + SAR: 3.7506, + SBD: 8.16, + SCR: 13.7114, + SDG: 574.1082, + SEK: 10.4358, + SGD: 1.3266, + SHP: 0.773275595422208, + SLL: 19385.959, + SOS: 569.1501, + SVC: 8.75, + SZL: 17.6615, + THB: 34.2605, + TND: 3.0573, + TOP: 2.3249, + TRY: 26.9502, + TTD: 6.7814, + TUSD: 1.00091683982528, + TWD: 31.191, + TZS: 2404.7, + UAH: 36.8309, + UGX: 3640.025, + USB: 1, + USDC: 0.985940488632106, + USDK: 1, + UST: 0.998552099455789, + UYU: 40.675, + VND: 23651.7, + WST: 2.6921, + XAF: 592.2489, + XCD: 2.7, + XOF: 592.2489, + XPF: 107.6633, + YER: 250.2405, + ZAR: 17.6615, + ZMK: 18825, + eUSDT: 0.998552099455789, + tUSDT: 0.998552099455789, + }, + }, + msg_type: 'exchange_rates', + req_id: context.req_id, + subscription: { + id: '69eddd7e-9cf2-48d2-e951-a7008e724c2f', + }, + }; + } +} diff --git a/packages/integration/src/mocks/general/index.ts b/packages/integration/src/mocks/general/index.ts new file mode 100644 index 000000000000..416527e54d65 --- /dev/null +++ b/packages/integration/src/mocks/general/index.ts @@ -0,0 +1,14 @@ +import mock_exchange_rates from './exchange_rates'; +import mock_payout_currencies from './payout_currencies'; +import mock_time from './time'; +import mock_website_status from './website_status'; +import { Context } from 'Utils/mocks/mocks'; + +const general = (context: Context) => { + mock_exchange_rates(context); + mock_payout_currencies(context); + mock_time(context); + mock_website_status(context); +}; + +export default general; diff --git a/packages/integration/src/mocks/general/payout_currencies.ts b/packages/integration/src/mocks/general/payout_currencies.ts new file mode 100644 index 000000000000..4535eddfdba5 --- /dev/null +++ b/packages/integration/src/mocks/general/payout_currencies.ts @@ -0,0 +1,15 @@ +import { Context } from 'Utils/mocks/mocks'; + +export default function mock_payout_currencies(context: Context) { + if ('payout_currencies' in context.request && context.request.payout_currencies === 1) { + context.response = { + echo_req: { + req_id: context.req_id, + payout_currencies: 1, + }, + req_id: context.req_id, + msg_type: 'payout_currencies', + payout_currencies: ['USD'], + }; + } +} diff --git a/packages/integration/src/mocks/general/time.ts b/packages/integration/src/mocks/general/time.ts new file mode 100644 index 000000000000..47c31e5500e9 --- /dev/null +++ b/packages/integration/src/mocks/general/time.ts @@ -0,0 +1,15 @@ +import { Context } from 'Utils/mocks/mocks'; + +export default function mock_time(context: Context) { + if ('time' in context.request && context.request.time === 1) { + context.response = { + echo_req: { + req_id: context.req_id, + time: 1, + }, + req_id: context.req_id, + msg_type: 'time', + time: parseInt((Date.now() / 1000).toFixed(0)), + }; + } +} diff --git a/packages/integration/src/mocks/general/website_status.ts b/packages/integration/src/mocks/general/website_status.ts new file mode 100644 index 000000000000..436c9d8017fa --- /dev/null +++ b/packages/integration/src/mocks/general/website_status.ts @@ -0,0 +1,1864 @@ +import { Context } from 'Utils/mocks/mocks'; + +export default function mock_website_status(context: Context) { + if ('website_status' in context.request && context.request.website_status === 1) { + context.response = { + echo_req: { + req_id: context.req_id, + website_status: 1, + }, + msg_type: 'website_status', + req_id: context.req_id, + website_status: { + api_call_limits: { + max_proposal_subscription: { + applies_to: 'subscribing to proposal concurrently', + max: 5, + }, + max_requestes_general: { + applies_to: 'rest of calls', + hourly: 14400, + minutely: 180, + }, + max_requests_outcome: { + applies_to: 'portfolio, statement and proposal', + hourly: 1500, + minutely: 25, + }, + max_requests_pricing: { + applies_to: 'proposal and proposal_open_contract', + hourly: 3600, + minutely: 80, + }, + }, + broker_codes: ['CRW', 'MX', 'VRW', 'CH', 'MFW', 'VRTC', 'VRCH', 'MLT', 'MF', 'CRA', 'CR'], + clients_country: 'my', + currencies_config: { + AUD: { + fractional_digits: 2, + is_deposit_suspended: 0, + is_suspended: 0, + is_withdrawal_suspended: 0, + name: 'Australian Dollar', + stake_default: 15, + transfer_between_accounts: { + fees: { + BTC: 2, + BUSD: 2, + DAI: 2, + ETH: 2, + EUR: 0, + EURS: 2, + GBP: 0, + IDK: 2, + LTC: 2, + PAX: 2, + TUSD: 2, + USB: 2, + USD: 0, + USDC: 2, + USDK: 2, + UST: 2, + eUSDT: 2, + tUSDT: 2, + }, + limits: { + max: 7402.03, + min: 1.48, + }, + limits_ctrader: { + max: 22206.1, + min: 0.01, + }, + limits_derivez: { + max: 22206.1, + min: 0.01, + }, + limits_dxtrade: { + max: 3701.02, + min: 0.01, + }, + limits_mt5: { + max: 22206.1, + min: 0.01, + }, + }, + type: 'fiat', + }, + BTC: { + fractional_digits: 8, + is_deposit_suspended: 0, + is_suspended: 0, + is_withdrawal_suspended: 0, + name: 'Bitcoin', + stake_default: 0.0004, + transfer_between_accounts: { + fees: { + AUD: 2, + BUSD: 2, + DAI: 2, + ETH: 2, + EUR: 2, + EURS: 2, + GBP: 2, + IDK: 2, + LTC: 2, + PAX: 2, + TUSD: 2, + USB: 2, + USD: 2, + USDC: 2, + USDK: 2, + UST: 2, + eUSDT: 2, + tUSDT: 2, + }, + limits: { + max: 0.17060625, + min: 3.412e-5, + }, + limits_ctrader: { + max: 0.51181875, + min: 3.4e-7, + }, + limits_derivez: { + max: 0.51181875, + min: 3.4e-7, + }, + limits_dxtrade: { + max: 0.08530312, + min: 3.4e-7, + }, + limits_mt5: { + max: 0.51181875, + min: 3.4e-7, + }, + }, + type: 'crypto', + }, + BUSD: { + fractional_digits: 2, + is_deposit_suspended: 1, + is_suspended: 1, + is_withdrawal_suspended: 1, + name: 'Binance USD', + stake_default: 10, + transfer_between_accounts: { + fees: { + AUD: 2, + BTC: 2, + DAI: 2, + ETH: 2, + EUR: 2, + EURS: 2, + GBP: 2, + IDK: 2, + LTC: 2, + PAX: 2, + TUSD: 2, + USB: 2, + USD: 2, + USDC: 2, + USDK: 2, + UST: 2, + eUSDT: 2, + tUSDT: 2, + }, + limits: { + max: 5000.5, + min: 1, + }, + limits_ctrader: { + max: 15001.5, + min: 0.01, + }, + limits_derivez: { + max: 15001.5, + min: 0.01, + }, + limits_dxtrade: { + max: 2500.25, + min: 0.01, + }, + limits_mt5: { + max: 15001.5, + min: 0.01, + }, + }, + type: 'crypto', + }, + DAI: { + fractional_digits: 2, + is_deposit_suspended: 1, + is_suspended: 1, + is_withdrawal_suspended: 1, + name: 'Multi-Collateral DAI', + stake_default: 10, + transfer_between_accounts: { + fees: { + AUD: 2, + BTC: 2, + BUSD: 2, + ETH: 2, + EUR: 2, + EURS: 2, + GBP: 2, + IDK: 2, + LTC: 2, + PAX: 2, + TUSD: 2, + USB: 2, + USD: 2, + USDC: 2, + USDK: 2, + UST: 2, + eUSDT: 2, + tUSDT: 2, + }, + limits: { + max: 5000, + min: 1, + }, + limits_ctrader: { + max: 15000, + min: 0.01, + }, + limits_derivez: { + max: 15000, + min: 0.01, + }, + limits_dxtrade: { + max: 2500, + min: 0.01, + }, + limits_mt5: { + max: 15000, + min: 0.01, + }, + }, + type: 'crypto', + }, + ETH: { + fractional_digits: 8, + is_deposit_suspended: 0, + is_suspended: 0, + is_withdrawal_suspended: 0, + name: 'Ethereum', + stake_default: 0.005, + transfer_between_accounts: { + fees: { + AUD: 2, + BTC: 2, + BUSD: 2, + DAI: 2, + EUR: 2, + EURS: 2, + GBP: 2, + IDK: 2, + LTC: 2, + PAX: 2, + TUSD: 2, + USB: 2, + USD: 2, + USDC: 2, + USDK: 2, + UST: 2, + eUSDT: 2, + tUSDT: 2, + }, + limits: { + max: 2.68833826, + min: 0.00053767, + }, + limits_ctrader: { + max: 8.06501477, + min: 5.38e-6, + }, + limits_derivez: { + max: 8.06501477, + min: 5.38e-6, + }, + limits_dxtrade: { + max: 1.34416913, + min: 5.38e-6, + }, + limits_mt5: { + max: 8.06501477, + min: 5.38e-6, + }, + }, + type: 'crypto', + }, + EUR: { + fractional_digits: 2, + is_deposit_suspended: 0, + is_suspended: 0, + is_withdrawal_suspended: 0, + name: 'Euro', + stake_default: 10, + transfer_between_accounts: { + fees: { + AUD: 0, + BTC: 2, + BUSD: 2, + DAI: 2, + ETH: 2, + EURS: 2, + GBP: 0, + IDK: 2, + LTC: 2, + PAX: 2, + TUSD: 2, + USB: 2, + USD: 0, + USDC: 2, + USDK: 2, + UST: 2, + eUSDT: 2, + tUSDT: 2, + }, + limits: { + max: 4513.78, + min: 0.9, + }, + limits_ctrader: { + max: 13541.33, + min: 0.01, + }, + limits_derivez: { + max: 13541.33, + min: 0.01, + }, + limits_dxtrade: { + max: 2256.89, + min: 0.01, + }, + limits_mt5: { + max: 13541.33, + min: 0.01, + }, + }, + type: 'fiat', + }, + EURS: { + fractional_digits: 2, + is_deposit_suspended: 1, + is_suspended: 1, + is_withdrawal_suspended: 1, + name: 'STATIS Euro', + stake_default: 10, + transfer_between_accounts: { + fees: { + AUD: 2, + BTC: 2, + BUSD: 2, + DAI: 2, + ETH: 2, + EUR: 2, + GBP: 2, + IDK: 2, + LTC: 2, + PAX: 2, + TUSD: 2, + USB: 2, + USD: 2, + USDC: 2, + USDK: 2, + UST: 2, + eUSDT: 2, + tUSDT: 2, + }, + limits: { + max: 4262.39, + min: 0.85, + }, + limits_ctrader: { + max: 12787.18, + min: 0.01, + }, + limits_derivez: { + max: 12787.18, + min: 0.01, + }, + limits_dxtrade: { + max: 2131.2, + min: 0.01, + }, + limits_mt5: { + max: 12787.18, + min: 0.01, + }, + }, + type: 'crypto', + }, + GBP: { + fractional_digits: 2, + is_deposit_suspended: 0, + is_suspended: 0, + is_withdrawal_suspended: 0, + name: 'Pound Sterling', + stake_default: 8, + transfer_between_accounts: { + fees: { + AUD: 0, + BTC: 2, + BUSD: 2, + DAI: 2, + ETH: 2, + EUR: 0, + EURS: 2, + IDK: 2, + LTC: 2, + PAX: 2, + TUSD: 2, + USB: 2, + USD: 0, + USDC: 2, + USDK: 2, + UST: 2, + eUSDT: 2, + tUSDT: 2, + }, + limits: { + max: 3867.16, + min: 0.77, + }, + limits_ctrader: { + max: 11601.47, + min: 0.01, + }, + limits_derivez: { + max: 11601.47, + min: 0.01, + }, + limits_dxtrade: { + max: 1933.58, + min: 0.01, + }, + limits_mt5: { + max: 11601.47, + min: 0.01, + }, + }, + type: 'fiat', + }, + IDK: { + fractional_digits: 0, + is_deposit_suspended: 1, + is_suspended: 1, + is_withdrawal_suspended: 1, + name: 'IDK', + stake_default: 150, + transfer_between_accounts: { + fees: { + AUD: 2, + BTC: 2, + BUSD: 2, + DAI: 2, + ETH: 2, + EUR: 2, + EURS: 2, + GBP: 2, + LTC: 2, + PAX: 2, + TUSD: 2, + USB: 2, + USD: 2, + USDC: 2, + USDK: 2, + UST: 2, + eUSDT: 2, + tUSDT: 2, + }, + limits: { + max: 75163, + min: 15, + }, + limits_ctrader: { + max: 225488, + min: 0, + }, + limits_derivez: { + max: 225488, + min: 0, + }, + limits_dxtrade: { + max: 37581, + min: 0, + }, + limits_mt5: { + max: 225488, + min: 0, + }, + }, + type: 'crypto', + }, + LTC: { + fractional_digits: 8, + is_deposit_suspended: 0, + is_suspended: 0, + is_withdrawal_suspended: 0, + name: 'Litecoin', + stake_default: 0.13, + transfer_between_accounts: { + fees: { + AUD: 2, + BTC: 2, + BUSD: 2, + DAI: 2, + ETH: 2, + EUR: 2, + EURS: 2, + GBP: 2, + IDK: 2, + PAX: 2, + TUSD: 2, + USB: 2, + USD: 2, + USDC: 2, + USDK: 2, + UST: 2, + eUSDT: 2, + tUSDT: 2, + }, + limits: { + max: 55.79111805, + min: 0.01115822, + }, + limits_ctrader: { + max: 167.37335416, + min: 0.00011158, + }, + limits_derivez: { + max: 167.37335416, + min: 0.00011158, + }, + limits_dxtrade: { + max: 27.89555903, + min: 0.00011158, + }, + limits_mt5: { + max: 167.37335416, + min: 0.00011158, + }, + }, + type: 'crypto', + }, + PAX: { + fractional_digits: 2, + is_deposit_suspended: 1, + is_suspended: 1, + is_withdrawal_suspended: 1, + name: 'Paxos Standard', + stake_default: 10, + transfer_between_accounts: { + fees: { + AUD: 2, + BTC: 2, + BUSD: 2, + DAI: 2, + ETH: 2, + EUR: 2, + EURS: 2, + GBP: 2, + IDK: 2, + LTC: 2, + TUSD: 2, + USB: 2, + USD: 2, + USDC: 2, + USDK: 2, + UST: 2, + eUSDT: 2, + tUSDT: 2, + }, + limits: { + max: 5002.81, + min: 1, + }, + limits_ctrader: { + max: 15008.43, + min: 0.01, + }, + limits_derivez: { + max: 15008.43, + min: 0.01, + }, + limits_dxtrade: { + max: 2501.41, + min: 0.01, + }, + limits_mt5: { + max: 15008.43, + min: 0.01, + }, + }, + type: 'crypto', + }, + TUSD: { + fractional_digits: 2, + is_deposit_suspended: 1, + is_suspended: 1, + is_withdrawal_suspended: 1, + name: 'True USD', + stake_default: 10, + transfer_between_accounts: { + fees: { + AUD: 2, + BTC: 2, + BUSD: 2, + DAI: 2, + ETH: 2, + EUR: 2, + EURS: 2, + GBP: 2, + IDK: 2, + LTC: 2, + PAX: 2, + USB: 2, + USD: 2, + USDC: 2, + USDK: 2, + UST: 2, + eUSDT: 2, + tUSDT: 2, + }, + limits: { + max: 5004.58, + min: 1, + }, + limits_ctrader: { + max: 15013.75, + min: 0.01, + }, + limits_derivez: { + max: 15013.75, + min: 0.01, + }, + limits_dxtrade: { + max: 2502.29, + min: 0.01, + }, + limits_mt5: { + max: 15013.75, + min: 0.01, + }, + }, + type: 'crypto', + }, + USB: { + fractional_digits: 2, + is_deposit_suspended: 1, + is_suspended: 1, + is_withdrawal_suspended: 1, + name: 'Binary Coin', + stake_default: 10, + transfer_between_accounts: { + fees: { + AUD: 2, + BTC: 2, + BUSD: 2, + DAI: 2, + ETH: 2, + EUR: 2, + EURS: 2, + GBP: 2, + IDK: 2, + LTC: 2, + PAX: 2, + TUSD: 2, + USD: 2, + USDC: 2, + USDK: 2, + UST: 2, + eUSDT: 2, + tUSDT: 2, + }, + limits: { + max: 5000, + min: 1, + }, + limits_ctrader: { + max: 15000, + min: 0.01, + }, + limits_derivez: { + max: 15000, + min: 0.01, + }, + limits_dxtrade: { + max: 2500, + min: 0.01, + }, + limits_mt5: { + max: 15000, + min: 0.01, + }, + }, + type: 'crypto', + }, + USD: { + fractional_digits: 2, + is_deposit_suspended: 0, + is_suspended: 0, + is_withdrawal_suspended: 0, + name: 'US Dollar', + stake_default: 10, + transfer_between_accounts: { + fees: { + AUD: 0, + BTC: 2, + BUSD: 2, + DAI: 2, + ETH: 2, + EUR: 0, + EURS: 2, + GBP: 0, + IDK: 2, + LTC: 2, + PAX: 2, + TUSD: 2, + USB: 2, + USDC: 2, + USDK: 2, + UST: 2, + eUSDT: 2, + tUSDT: 2, + }, + limits: { + max: 5000, + min: 1, + }, + limits_ctrader: { + max: 15000, + min: 0.01, + }, + limits_derivez: { + max: 15000, + min: 0.01, + }, + limits_dxtrade: { + max: 2500, + min: 0.01, + }, + limits_mt5: { + max: 15000, + min: 0.01, + }, + }, + type: 'fiat', + }, + USDC: { + fractional_digits: 2, + is_deposit_suspended: 0, + is_suspended: 0, + is_withdrawal_suspended: 0, + name: 'USD Coin', + stake_default: 10, + transfer_between_accounts: { + fees: { + AUD: 2, + BTC: 2, + BUSD: 2, + DAI: 2, + ETH: 2, + EUR: 2, + EURS: 2, + GBP: 2, + IDK: 2, + LTC: 2, + PAX: 2, + TUSD: 2, + USB: 2, + USD: 2, + USDK: 2, + UST: 2, + eUSDT: 2, + tUSDT: 2, + }, + limits: { + max: 4940, + min: 0.99, + }, + limits_ctrader: { + max: 14820.01, + min: 0.01, + }, + limits_derivez: { + max: 14820.01, + min: 0.01, + }, + limits_dxtrade: { + max: 2470, + min: 0.01, + }, + limits_mt5: { + max: 14820.01, + min: 0.01, + }, + }, + type: 'crypto', + }, + USDK: { + fractional_digits: 2, + is_deposit_suspended: 1, + is_suspended: 1, + is_withdrawal_suspended: 1, + name: 'USDK', + stake_default: 10, + transfer_between_accounts: { + fees: { + AUD: 2, + BTC: 2, + BUSD: 2, + DAI: 2, + ETH: 2, + EUR: 2, + EURS: 2, + GBP: 2, + IDK: 2, + LTC: 2, + PAX: 2, + TUSD: 2, + USB: 2, + USD: 2, + USDC: 2, + UST: 2, + eUSDT: 2, + tUSDT: 2, + }, + limits: { + max: 5000, + min: 1, + }, + limits_ctrader: { + max: 15000, + min: 0.01, + }, + limits_derivez: { + max: 15000, + min: 0.01, + }, + limits_dxtrade: { + max: 2500, + min: 0.01, + }, + limits_mt5: { + max: 15000, + min: 0.01, + }, + }, + type: 'crypto', + }, + UST: { + fractional_digits: 2, + is_deposit_suspended: 0, + is_suspended: 0, + is_withdrawal_suspended: 0, + name: 'Tether Omni', + stake_default: 10, + transfer_between_accounts: { + fees: { + AUD: 2, + BTC: 2, + BUSD: 2, + DAI: 2, + ETH: 2, + EUR: 2, + EURS: 2, + GBP: 2, + IDK: 2, + LTC: 2, + PAX: 2, + TUSD: 2, + USB: 2, + USD: 2, + USDC: 2, + USDK: 2, + eUSDT: 2, + tUSDT: 2, + }, + limits: { + max: 4992.76, + min: 1, + }, + limits_ctrader: { + max: 14978.28, + min: 0.01, + }, + limits_derivez: { + max: 14978.28, + min: 0.01, + }, + limits_dxtrade: { + max: 2496.38, + min: 0.01, + }, + limits_mt5: { + max: 14978.28, + min: 0.01, + }, + }, + type: 'crypto', + }, + eUSDT: { + fractional_digits: 2, + is_deposit_suspended: 0, + is_suspended: 0, + is_withdrawal_suspended: 0, + name: 'Tether ERC20', + stake_default: 10, + transfer_between_accounts: { + fees: { + AUD: 2, + BTC: 2, + BUSD: 2, + DAI: 2, + ETH: 2, + EUR: 2, + EURS: 2, + GBP: 2, + IDK: 2, + LTC: 2, + PAX: 2, + TUSD: 2, + USB: 2, + USD: 2, + USDC: 2, + USDK: 2, + UST: 2, + tUSDT: 2, + }, + limits: { + max: 4992.76, + min: 1, + }, + limits_ctrader: { + max: 14978.28, + min: 0.01, + }, + limits_derivez: { + max: 14978.28, + min: 0.01, + }, + limits_dxtrade: { + max: 2496.38, + min: 0.01, + }, + limits_mt5: { + max: 14978.28, + min: 0.01, + }, + }, + type: 'crypto', + }, + tUSDT: { + fractional_digits: 2, + is_deposit_suspended: 1, + is_suspended: 1, + is_withdrawal_suspended: 1, + name: 'Tether TRC20', + stake_default: 10, + transfer_between_accounts: { + fees: { + AUD: 2, + BTC: 2, + BUSD: 2, + DAI: 2, + ETH: 2, + EUR: 2, + EURS: 2, + GBP: 2, + IDK: 2, + LTC: 2, + PAX: 2, + TUSD: 2, + USB: 2, + USD: 2, + USDC: 2, + USDK: 2, + UST: 2, + eUSDT: 2, + }, + limits: { + max: 4992.76, + min: 1, + }, + limits_ctrader: { + max: 14978.28, + min: 0.01, + }, + limits_derivez: { + max: 14978.28, + min: 0.01, + }, + limits_dxtrade: { + max: 2496.38, + min: 0.01, + }, + limits_mt5: { + max: 14978.28, + min: 0.01, + }, + }, + type: 'crypto', + }, + }, + dxtrade_status: { + all: 0, + demo: 0, + real: 0, + }, + mt5_status: { + demo: [ + { + all: 0, + platform: 1, + server_number: 1, + }, + { + all: 0, + platform: 1, + server_number: 2, + }, + { + all: 0, + platform: 1, + server_number: 3, + }, + { + all: 0, + platform: 1, + server_number: 4, + }, + ], + real: [ + { + all: 0, + deposits: 0, + platform: 1, + server_number: 1, + withdrawals: 0, + }, + { + all: 0, + deposits: 0, + platform: 1, + server_number: 2, + withdrawals: 0, + }, + { + all: 0, + deposits: 0, + platform: 1, + server_number: 3, + withdrawals: 0, + }, + { + all: 0, + deposits: 0, + platform: 1, + server_number: 4, + withdrawals: 0, + }, + { + all: 0, + deposits: 0, + platform: 2, + server_number: 1, + withdrawals: 0, + }, + { + all: 0, + deposits: 0, + platform: 2, + server_number: 2, + withdrawals: 0, + }, + ], + }, + p2p_config: { + adverts_active_limit: 3, + adverts_archive_period: 3, + block_trade: { + disabled: 1, + maximum_advert_amount: 20000, + }, + cancellation_block_duration: 24, + cancellation_count_period: 24, + cancellation_grace_period: 0, + cancellation_limit: 3, + cross_border_ads_enabled: 1, + disabled: 0, + feature_level: 2, + fixed_rate_adverts: 'disabled', + fixed_rate_adverts_end_date: '2022-08-31', + float_rate_adverts: 'enabled', + float_rate_offset_limit: 10, + local_currencies: [ + { + display_name: 'Antarctic Dollar', + has_adverts: 0, + symbol: 'AAD', + }, + { + display_name: 'Afghan Afghani', + has_adverts: 1, + symbol: 'AFN', + }, + { + display_name: 'Albanian Lek', + has_adverts: 0, + symbol: 'ALL', + }, + { + display_name: 'Armenian Dram', + has_adverts: 0, + symbol: 'AMD', + }, + { + display_name: 'Netherlands Antillean Guilder', + has_adverts: 0, + symbol: 'ANG', + }, + { + display_name: 'Angolan Kwanza', + has_adverts: 0, + symbol: 'AOA', + }, + { + display_name: 'Argentine Peso', + has_adverts: 1, + symbol: 'ARS', + }, + { + display_name: 'Australian Dollar', + has_adverts: 0, + symbol: 'AUD', + }, + { + display_name: 'Aruban Florin', + has_adverts: 0, + symbol: 'AWG', + }, + { + display_name: 'Azerbaijan Manat', + has_adverts: 0, + symbol: 'AZN', + }, + { + display_name: 'Convertible Mark', + has_adverts: 0, + symbol: 'BAM', + }, + { + display_name: 'Barbados Dollar', + has_adverts: 1, + symbol: 'BBD', + }, + { + display_name: 'Bangladeshi Taka', + has_adverts: 1, + symbol: 'BDT', + }, + { + display_name: 'Bahraini Dinar', + has_adverts: 0, + symbol: 'BHD', + }, + { + display_name: 'Burundi Franc', + has_adverts: 1, + symbol: 'BIF', + }, + { + display_name: 'Bermudian Dollar', + has_adverts: 0, + symbol: 'BMD', + }, + { + display_name: 'Brunei Dollar', + has_adverts: 0, + symbol: 'BND', + }, + { + display_name: 'Boliviano', + has_adverts: 1, + symbol: 'BOB', + }, + { + display_name: 'Brazilian Real', + has_adverts: 1, + symbol: 'BRL', + }, + { + display_name: 'Bahamian Dollar', + has_adverts: 0, + symbol: 'BSD', + }, + { + display_name: 'Bhutanese Ngultrum', + has_adverts: 0, + symbol: 'BTN', + }, + { + display_name: 'Botswana Pula', + has_adverts: 1, + symbol: 'BWP', + }, + { + display_name: 'Belarusian Ruble', + has_adverts: 0, + symbol: 'BYN', + }, + { + display_name: 'Belize Dollar', + has_adverts: 0, + symbol: 'BZD', + }, + { + display_name: 'Congolese Franc', + has_adverts: 1, + symbol: 'CDF', + }, + { + display_name: 'Swiss Franc', + has_adverts: 0, + symbol: 'CHF', + }, + { + display_name: 'Chilean Peso', + has_adverts: 1, + symbol: 'CLP', + }, + { + display_name: 'Yuan Renminbi', + has_adverts: 1, + symbol: 'CNY', + }, + { + display_name: 'Colombian Peso', + has_adverts: 1, + symbol: 'COP', + }, + { + display_name: 'Costa Rican Colon', + has_adverts: 1, + symbol: 'CRC', + }, + { + display_name: 'Cabo Verde Escudo', + has_adverts: 0, + symbol: 'CVE', + }, + { + display_name: 'Djibouti Franc', + has_adverts: 0, + symbol: 'DJF', + }, + { + display_name: 'Danish Krone', + has_adverts: 0, + symbol: 'DKK', + }, + { + display_name: 'Dominican Peso', + has_adverts: 1, + symbol: 'DOP', + }, + { + display_name: 'Algerian Dinar', + has_adverts: 0, + symbol: 'DZD', + }, + { + display_name: 'Ecuadorian Sucre', + has_adverts: 0, + symbol: 'ECS', + }, + { + display_name: 'Egyptian Pound', + has_adverts: 0, + symbol: 'EGP', + }, + { + display_name: 'Eritrean Nakfa', + has_adverts: 0, + symbol: 'ERN', + }, + { + display_name: 'Ethiopian Birr', + has_adverts: 0, + symbol: 'ETB', + }, + { + display_name: 'Euro', + has_adverts: 0, + symbol: 'EUR', + }, + { + display_name: 'Fiji Dollar', + has_adverts: 0, + symbol: 'FJD', + }, + { + display_name: 'Falkland Islands Pound', + has_adverts: 0, + symbol: 'FKP', + }, + { + display_name: 'Pound Sterling', + has_adverts: 0, + symbol: 'GBP', + }, + { + display_name: 'Georgian Lari', + has_adverts: 0, + symbol: 'GEL', + }, + { + display_name: 'Ghanaian Cedi (old)', + has_adverts: 0, + symbol: 'GHC', + }, + { + display_name: 'Ghana Cedi', + has_adverts: 1, + symbol: 'GHS', + }, + { + display_name: 'Gibraltar Pound', + has_adverts: 0, + symbol: 'GIP', + }, + { + display_name: 'Gambian Dalasi', + has_adverts: 0, + symbol: 'GMD', + }, + { + display_name: 'Guinean Franc', + has_adverts: 1, + symbol: 'GNF', + }, + { + display_name: 'Guatemalan Quetzal', + has_adverts: 0, + symbol: 'GTQ', + }, + { + display_name: 'Guyana Dollar', + has_adverts: 0, + symbol: 'GYD', + }, + { + display_name: 'Honduran Lempira', + has_adverts: 0, + symbol: 'HNL', + }, + { + display_name: 'Haitian Gourde', + has_adverts: 1, + symbol: 'HTG', + }, + { + display_name: 'Indonesian Rupiah', + has_adverts: 1, + symbol: 'IDR', + }, + { + display_name: 'New Israeli Sheqel', + has_adverts: 0, + symbol: 'ILS', + }, + { + display_name: 'Indian Rupee', + has_adverts: 1, + symbol: 'INR', + }, + { + display_name: 'Iraqi Dinar', + has_adverts: 0, + symbol: 'IQD', + }, + { + display_name: 'Iceland Krona', + has_adverts: 0, + symbol: 'ISK', + }, + { + display_name: 'Jamaican Dollar', + has_adverts: 1, + symbol: 'JMD', + }, + { + display_name: 'Jordanian Dinar', + has_adverts: 0, + symbol: 'JOD', + }, + { + display_name: 'Japanese Yen', + has_adverts: 0, + symbol: 'JPY', + }, + { + display_name: 'Kenyan Shilling', + has_adverts: 1, + symbol: 'KES', + }, + { + display_name: 'Kyrgyzstani Som', + has_adverts: 0, + symbol: 'KGS', + }, + { + display_name: 'Cambodian Riel', + has_adverts: 0, + symbol: 'KHR', + }, + { + display_name: 'Comorian Franc', + has_adverts: 0, + symbol: 'KMF', + }, + { + display_name: 'South Korean Won', + has_adverts: 0, + symbol: 'KRW', + }, + { + display_name: 'Kuwaiti Dinar', + has_adverts: 0, + symbol: 'KWD', + }, + { + display_name: 'Kazakhstani Tenge', + has_adverts: 0, + symbol: 'KZT', + }, + { + display_name: 'Lao Kip', + has_adverts: 0, + symbol: 'LAK', + }, + { + display_name: 'Lebanese Pound', + has_adverts: 0, + symbol: 'LBP', + }, + { + display_name: 'Sri Lanka Rupee', + has_adverts: 1, + symbol: 'LKR', + }, + { + display_name: 'Liberian Dollar', + has_adverts: 1, + symbol: 'LRD', + }, + { + display_name: 'Lesotho Loti', + has_adverts: 0, + symbol: 'LSL', + }, + { + display_name: 'Libyan Dinar', + has_adverts: 0, + symbol: 'LYD', + }, + { + display_name: 'Moroccan Dirham', + has_adverts: 0, + symbol: 'MAD', + }, + { + display_name: 'Moldovan Leu', + has_adverts: 0, + symbol: 'MDL', + }, + { + display_name: 'Malagasy Ariary', + has_adverts: 0, + symbol: 'MGA', + }, + { + display_name: 'Macedonian Denar', + has_adverts: 0, + symbol: 'MKD', + }, + { + display_name: 'Mongolian Tögrög', + has_adverts: 0, + symbol: 'MNT', + }, + { + display_name: 'Macanese Pataca', + has_adverts: 0, + symbol: 'MOP', + }, + { + display_name: 'Mauritanian Ouguiya', + has_adverts: 0, + symbol: 'MRU', + }, + { + display_name: 'Mauritius Rupee', + has_adverts: 1, + symbol: 'MUR', + }, + { + display_name: 'Maldivian Rufiyaa', + has_adverts: 0, + symbol: 'MVR', + }, + { + display_name: 'Malawi Kwacha', + has_adverts: 1, + symbol: 'MWK', + }, + { + display_name: 'Mexican Peso', + has_adverts: 1, + symbol: 'MXN', + }, + { + display_name: 'Mozambique Metical (old)', + has_adverts: 0, + symbol: 'MZM', + }, + { + display_name: 'Mozambique Metical', + has_adverts: 1, + symbol: 'MZN', + }, + { + display_name: 'Namibia Dollar', + has_adverts: 1, + symbol: 'NAD', + }, + { + display_name: 'Cordoba Oro', + has_adverts: 1, + symbol: 'NIO', + }, + { + display_name: 'Norwegian Krone', + has_adverts: 0, + symbol: 'NOK', + }, + { + display_name: 'Nepalese Rupee', + has_adverts: 0, + symbol: 'NPR', + }, + { + display_name: 'New Zealand Dollar', + has_adverts: 1, + symbol: 'NZD', + }, + { + display_name: 'Omani Rial', + has_adverts: 1, + symbol: 'OMR', + }, + { + display_name: 'Panamanian Balboa', + has_adverts: 1, + symbol: 'PAB', + }, + { + display_name: 'Peruvian Sol', + has_adverts: 1, + symbol: 'PEN', + }, + { + display_name: 'Papua New Guinean Kina', + has_adverts: 0, + symbol: 'PGK', + }, + { + display_name: 'Philippine Peso', + has_adverts: 1, + symbol: 'PHP', + }, + { + display_name: 'Pakistan Rupee', + has_adverts: 1, + symbol: 'PKR', + }, + { + display_name: 'Qatari Riyal', + has_adverts: 1, + symbol: 'QAR', + }, + { + display_name: 'Serbian Dinar', + has_adverts: 0, + symbol: 'RSD', + }, + { + display_name: 'Russian Ruble', + has_adverts: 1, + symbol: 'RUB', + }, + { + display_name: 'Saudi Riyal', + has_adverts: 0, + symbol: 'SAR', + }, + { + display_name: 'Solomon Islands Dollar', + has_adverts: 0, + symbol: 'SBD', + }, + { + display_name: 'Seychelles Rupee', + has_adverts: 0, + symbol: 'SCR', + }, + { + display_name: 'Sudanese Pound', + has_adverts: 0, + symbol: 'SDG', + }, + { + display_name: 'Singapore Dollar', + has_adverts: 0, + symbol: 'SGD', + }, + { + display_name: 'Saint Helena Pound', + has_adverts: 0, + symbol: 'SHP', + }, + { + display_name: 'Sierra Leonean Leone', + has_adverts: 0, + symbol: 'SLL', + }, + { + display_name: 'Somali Shilling', + has_adverts: 1, + symbol: 'SOS', + }, + { + display_name: 'Surinam Dollar', + has_adverts: 0, + symbol: 'SRD', + }, + { + display_name: 'South Sudanese Pound', + has_adverts: 0, + symbol: 'SSP', + }, + { + display_name: 'São Tomé and Príncipe Dobra', + has_adverts: 0, + symbol: 'STN', + }, + { + display_name: 'El Salvador Colon', + has_adverts: 0, + symbol: 'SVC', + }, + { + display_name: 'Swazi Lilangeni', + has_adverts: 1, + symbol: 'SZL', + }, + { + display_name: 'Thai Baht', + has_adverts: 0, + is_default: 1, + symbol: 'THB', + }, + { + display_name: 'Tajikistani Somoni', + has_adverts: 0, + symbol: 'TJS', + }, + { + display_name: 'Turkmenistan New Manat', + has_adverts: 0, + symbol: 'TMT', + }, + { + display_name: 'Tunisian Dinar', + has_adverts: 0, + symbol: 'TND', + }, + { + display_name: 'Tongan Paʻanga', + has_adverts: 0, + symbol: 'TOP', + }, + { + display_name: 'Turkish Lira', + has_adverts: 1, + symbol: 'TRY', + }, + { + display_name: 'Trinidad and Tobago Dollar', + has_adverts: 0, + symbol: 'TTD', + }, + { + display_name: 'New Taiwan Dollar', + has_adverts: 0, + symbol: 'TWD', + }, + { + display_name: 'Tanzanian Shilling', + has_adverts: 1, + symbol: 'TZS', + }, + { + display_name: 'Ukrainian Hryvnia', + has_adverts: 0, + symbol: 'UAH', + }, + { + display_name: 'Uganda Shilling', + has_adverts: 1, + symbol: 'UGX', + }, + { + display_name: 'US Dollar', + has_adverts: 1, + symbol: 'USD', + }, + { + display_name: 'Uruguayan Peso', + has_adverts: 1, + symbol: 'UYU', + }, + { + display_name: 'Uzbekistan Sum', + has_adverts: 0, + symbol: 'UZS', + }, + { + display_name: 'Venezuelan Bolívar', + has_adverts: 0, + symbol: 'VEB', + }, + { + display_name: 'Venezuelan Bolívar Fuente', + has_adverts: 0, + symbol: 'VEF', + }, + { + display_name: 'Venezuelan Bolívar Soberano', + has_adverts: 1, + symbol: 'VES', + }, + { + display_name: 'Vietnamese Đồng', + has_adverts: 0, + symbol: 'VND', + }, + { + display_name: 'Samoan Tala', + has_adverts: 0, + symbol: 'WST', + }, + { + display_name: 'CFA Franc BEAC', + has_adverts: 1, + symbol: 'XAF', + }, + { + display_name: 'East Caribbean Dollar', + has_adverts: 0, + symbol: 'XCD', + }, + { + display_name: 'CFA Franc BCEAO', + has_adverts: 1, + symbol: 'XOF', + }, + { + display_name: 'CFP Franc', + has_adverts: 0, + symbol: 'XPF', + }, + { + display_name: 'Yemeni Rial', + has_adverts: 0, + symbol: 'YER', + }, + { + display_name: 'South African Rand', + has_adverts: 1, + symbol: 'ZAR', + }, + { + display_name: 'Zambian Kwacha (old)', + has_adverts: 0, + symbol: 'ZMK', + }, + { + display_name: 'Zambian Kwacha', + has_adverts: 1, + symbol: 'ZMW', + }, + { + display_name: 'Zimbabwe Dollar', + has_adverts: 0, + symbol: 'ZWD', + }, + { + display_name: 'Zimbabwe Dollar', + has_adverts: 1, + symbol: 'ZWL', + }, + ], + maximum_advert_amount: 5001, + maximum_order_amount: 1000, + order_daily_limit: 200, + order_payment_period: 60, + payment_methods_enabled: 1, + review_period: 24, + supported_currencies: ['usd'], + }, + payment_agents: { + initial_deposit_per_country: { + BR: 3000, + BW: 2000, + ID: 3000, + IN: 3000, + KE: 3000, + LK: 3000, + NG: 3000, + TZ: 3000, + ZA: 3000, + ZW: 2000, + default: 500, + }, + }, + site_status: 'up', + supported_languages: [ + 'EN', + 'ID', + 'RU', + 'ES', + 'FR', + 'IT', + 'PT', + 'PL', + 'DE', + 'ZH_CN', + 'VI', + 'ZH_TW', + 'TH', + 'TR', + 'KO', + 'AR', + 'BN', + 'SI', + ], + terms_conditions_version: 'Version 4.2.0 2020-08-07', + }, + }; + } +} diff --git a/packages/integration/src/mocks/location/residents_list.ts b/packages/integration/src/mocks/location/residents_list.ts new file mode 100644 index 000000000000..ffcaa59e6b9e --- /dev/null +++ b/packages/integration/src/mocks/location/residents_list.ts @@ -0,0 +1,6972 @@ +import { Context } from 'Utils/mocks/mocks'; + +function mock_residents_list(context: Context) { + if ('residence_list' in context.request) { + context.response = { + echo_req: { + req_id: context.req_id, + residence_list: 1, + }, + msg_type: 'residence_list', + req_id: context.req_id, + residence_list: [ + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 0, + }, + }, + }, + phone_idd: '93', + text: 'Afghanistan', + value: 'af', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + }, + is_country_supported: 0, + }, + }, + }, + phone_idd: '35818', + text: 'Aland Islands', + value: 'ax', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '355', + text: 'Albania', + tin_format: ['^[A-Ta-t0-9]\\d{8}[A-Wa-w]$'], + value: 'al', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '213', + text: 'Algeria', + value: 'dz', + }, + { + disabled: 'DISABLED', + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '684', + text: 'American Samoa', + value: 'as', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '376', + text: 'Andorra', + tin_format: ['^[FfEeAaLlCcDdGgOoPpUu]\\d{6}[a-zA-Z]$'], + value: 'ad', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '244', + text: 'Angola', + value: 'ao', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '1264', + text: 'Anguilla', + value: 'ai', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: {}, + is_country_supported: 0, + }, + }, + }, + phone_idd: '672', + selected: 'selected', + text: 'Antarctica', + value: 'aq', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '1268', + text: 'Antigua and Barbuda', + tin_format: ['^\\d{6}$'], + value: 'ag', + }, + { + identity: { + services: { + idv: { + documents_supported: { + dni: { + display_name: 'Documento Nacional de Identidad', + format: '^\\d{7,8}$', + }, + }, + has_visual_sample: 0, + is_country_supported: 1, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '54', + text: 'Argentina', + tin_format: ['^(20|23|24|25|26|27|30|33)\\d{9}$'], + value: 'ar', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '374', + text: 'Armenia', + value: 'am', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '297', + text: 'Aruba', + tin_format: ['^\\d{8}$'], + value: 'aw', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + immigration_status_document: { + display_name: 'Immigration Status Document', + }, + national_health_insurance_card: { + display_name: 'National Health Insurance Card', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + visa: { + display_name: 'Visa', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '61', + text: 'Australia', + value: 'au', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + asylum_registration_card: { + display_name: 'Asylum Registration Card', + }, + driving_licence: { + display_name: 'Driving Licence', + }, + national_health_insurance_card: { + display_name: 'National Health Insurance Card', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + visa: { + display_name: 'Visa', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '43', + text: 'Austria', + tin_format: ['^\\d{9}$'], + value: 'at', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '994', + text: 'Azerbaijan', + tin_format: ['^\\d{10}$'], + value: 'az', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '1242', + text: 'Bahamas', + value: 'bs', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '973', + text: 'Bahrain', + value: 'bh', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '880', + text: 'Bangladesh', + value: 'bd', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '1246', + text: 'Barbados', + value: 'bb', + }, + { + disabled: 'DISABLED', + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + passport: { + display_name: 'Passport', + }, + visa: { + display_name: 'Visa', + }, + }, + is_country_supported: 0, + }, + }, + }, + phone_idd: '375', + text: 'Belarus', + value: 'by', + }, + { + disabled: 'DISABLED', + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_health_insurance_card: { + display_name: 'National Health Insurance Card', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + visa: { + display_name: 'Visa', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '32', + text: 'Belgium', + value: 'be', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '501', + text: 'Belize', + tin_format: ['^\\d{6}(10|13|66)$'], + value: 'bz', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '229', + text: 'Benin', + value: 'bj', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '1441', + text: 'Bermuda', + value: 'bm', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '975', + text: 'Bhutan', + value: 'bt', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '591', + text: 'Bolivia', + value: 'bo', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '387', + text: 'Bosnia and Herzegovina', + value: 'ba', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '267', + text: 'Botswana', + value: 'bw', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: {}, + is_country_supported: 0, + }, + }, + }, + text: 'Bouvet Island', + value: 'bv', + }, + { + identity: { + services: { + idv: { + documents_supported: { + cpf: { + display_name: 'CPF', + format: '^[0-9]{3}\\.[0-9]{3}\\.[0-9]{3}-[0-9]{2}$', + }, + }, + has_visual_sample: 0, + is_country_supported: 1, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '55', + text: 'Brazil', + tin_format: ['^(\\d{11}|\\d{14})$'], + value: 'br', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: {}, + is_country_supported: 0, + }, + }, + }, + phone_idd: '246', + text: 'British Indian Ocean Territory', + value: 'io', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '1284', + text: 'British Virgin Islands', + value: 'vg', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '673', + text: 'Brunei', + value: 'bn', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_health_insurance_card: { + display_name: 'National Health Insurance Card', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '359', + text: 'Bulgaria', + tin_format: ['^\\d{10}$'], + value: 'bg', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '226', + text: 'Burkina Faso', + value: 'bf', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '257', + text: 'Burundi', + value: 'bi', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + visa: { + display_name: 'Visa', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '855', + text: 'Cambodia', + value: 'kh', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '237', + text: 'Cameroon', + value: 'cm', + }, + { + disabled: 'DISABLED', + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_health_insurance_card: { + display_name: 'National Health Insurance Card', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + visa: { + display_name: 'Visa', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '1', + text: 'Canada', + value: 'ca', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '238', + text: 'Cape Verde', + value: 'cv', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: {}, + is_country_supported: 0, + }, + }, + }, + phone_idd: '599', + text: 'Caribbean Netherlands', + value: 'bq', + }, + { + disabled: 'DISABLED', + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '1345', + text: 'Cayman Islands', + value: 'ky', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '236', + text: 'Central African Republic', + value: 'cf', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '235', + text: 'Chad', + value: 'td', + }, + { + identity: { + services: { + idv: { + documents_supported: { + national_id: { + display_name: 'National ID Number', + format: '^\\d{8}[A-Za-z0-9]$', + }, + }, + has_visual_sample: 0, + is_country_supported: 1, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '56', + text: 'Chile', + tin_format: ['^\\d{7,8}[0-9Kk]$'], + value: 'cl', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + immigration_status_document: { + display_name: 'Immigration Status Document', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + visa: { + display_name: 'Visa', + }, + }, + is_country_supported: 0, + }, + }, + }, + phone_idd: '86', + text: 'China', + tin_format: [ + '^\\d{17}[Xx\\d]$', + '^[CcWwHhMmTt]\\d{16}[xX\\d]$', + '^[Jj]\\d{14}$', + '^(\\d{15}|\\d{18})$', + '^\\d{8}\\w{10}$', + ], + value: 'cn', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + }, + is_country_supported: 0, + }, + }, + }, + phone_idd: '618', + text: 'Christmas Island', + value: 'cx', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + }, + is_country_supported: 0, + }, + }, + }, + phone_idd: '6189162', + text: 'Cocos (Keeling) Islands', + value: 'cc', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '57', + text: 'Colombia', + tin_format: ['^\\d{8,10}$'], + value: 'co', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '269', + text: 'Comoros', + value: 'km', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '242', + text: 'Congo - Brazzaville', + value: 'cg', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 0, + }, + }, + }, + phone_idd: '243', + text: 'Congo - Kinshasa', + value: 'cd', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + }, + is_country_supported: 0, + }, + }, + }, + phone_idd: '682', + text: 'Cook Islands', + tin_format: ['^\\d{5}$'], + value: 'ck', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '506', + text: 'Costa Rica', + tin_format: ['^\\d{9,12}$'], + value: 'cr', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '225', + text: "Cote d'Ivoire", + value: 'ci', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_health_insurance_card: { + display_name: 'National Health Insurance Card', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '385', + text: 'Croatia', + tin_format: ['^\\d{11}$'], + value: 'hr', + }, + { + disabled: 'DISABLED', + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '53', + text: 'Cuba', + value: 'cu', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + }, + is_country_supported: 0, + }, + }, + }, + phone_idd: '5999', + text: 'Curacao', + value: 'cw', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_health_insurance_card: { + display_name: 'National Health Insurance Card', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '357', + text: 'Cyprus', + tin_format: ['^\\d{8}[A-Za-z]$'], + value: 'cy', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_health_insurance_card: { + display_name: 'National Health Insurance Card', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + visa: { + display_name: 'Visa', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '420', + text: 'Czech Republic', + tin_format: ['^\\d{9,10}$'], + value: 'cz', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_health_insurance_card: { + display_name: 'National Health Insurance Card', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + visa: { + display_name: 'Visa', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '45', + text: 'Denmark', + tin_format: ['^\\d{10}$'], + value: 'dk', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '253', + text: 'Djibouti', + value: 'dj', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '1767', + text: 'Dominica', + tin_format: ['^[1-9]\\d{5}$'], + value: 'dm', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '1809', + text: 'Dominican Republic', + value: 'do', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '593', + text: 'Ecuador', + tin_format: ['^\\d{10}001$', '^\\d{2}6\\d{6}0001$'], + value: 'ec', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + passport: { + display_name: 'Passport', + }, + visa: { + display_name: 'Visa', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '20', + text: 'Egypt', + value: 'eg', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '503', + text: 'El Salvador', + value: 'sv', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '240', + text: 'Equatorial Guinea', + value: 'gq', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '291', + text: 'Eritrea', + value: 'er', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_health_insurance_card: { + display_name: 'National Health Insurance Card', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + visa: { + display_name: 'Visa', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '372', + text: 'Estonia', + tin_format: ['^\\d{11}$'], + value: 'ee', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '251', + text: 'Ethiopia', + value: 'et', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: {}, + is_country_supported: 0, + }, + }, + }, + phone_idd: '500', + text: 'Falkland Islands', + value: 'fk', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '298', + text: 'Faroe Islands', + tin_format: ['^\\d{9}$'], + value: 'fo', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '679', + text: 'Fiji', + value: 'fj', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_health_insurance_card: { + display_name: 'National Health Insurance Card', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + visa: { + display_name: 'Visa', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '358', + text: 'Finland', + tin_format: ['^\\d{6}[-+A]\\d{3}\\w$'], + value: 'fi', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + asylum_registration_card: { + display_name: 'Asylum Registration Card', + }, + driving_licence: { + display_name: 'Driving Licence', + }, + national_health_insurance_card: { + display_name: 'National Health Insurance Card', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + visa: { + display_name: 'Visa', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '33', + text: 'France', + tin_format: ['^\\d{13}$'], + value: 'fr', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: {}, + is_country_supported: 0, + }, + }, + }, + phone_idd: '594', + text: 'French Guiana', + value: 'gf', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '689', + text: 'French Polynesia', + value: 'pf', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: {}, + is_country_supported: 0, + }, + }, + }, + text: 'French Southern Territories', + value: 'tf', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '241', + text: 'Gabon', + value: 'ga', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '220', + text: 'Gambia', + value: 'gm', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '995', + text: 'Georgia', + value: 'ge', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_health_insurance_card: { + display_name: 'National Health Insurance Card', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + visa: { + display_name: 'Visa', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '49', + text: 'Germany', + tin_format: ['^\\d{11}$'], + value: 'de', + }, + { + identity: { + services: { + idv: { + documents_supported: { + drivers_license: { + display_name: 'Drivers License', + format: '^((\\d{8}[a-zA-Z]{1}\\d{1})|([a-zA-Z]{1}\\d{7})|([a-zA-Z]{3}\\-\\d{8}\\-\\d{5}))$', + }, + passport: { + display_name: 'Passport', + format: '^(?i)G[a-zA-Z0-9]{7,9}$', + }, + ssnit: { + display_name: 'Social Security and National Insurance Trust (SSNIT)', + format: '^[a-zA-Z]{1}[0-9]{12,14}$', + }, + }, + has_visual_sample: 0, + is_country_supported: 1, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '233', + text: 'Ghana', + tin_format: ['^(GHA)-[0-9]{8,9}-[0-9]{1}$', '^(GRA|P00|C00|G00|Q00|V00)[A-Za-z0-9]{8}$'], + value: 'gh', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '350', + text: 'Gibraltar', + tin_format: ['^\\d{6}$'], + value: 'gi', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_health_insurance_card: { + display_name: 'National Health Insurance Card', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + visa: { + display_name: 'Visa', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '30', + text: 'Greece', + tin_format: ['^\\d{9}$'], + value: 'gr', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '299', + text: 'Greenland', + tin_format: ['^\\d{10}$'], + value: 'gl', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '1473', + text: 'Grenada', + tin_format: ['^\\d{8}$'], + value: 'gd', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: {}, + is_country_supported: 0, + }, + }, + }, + phone_idd: '590', + text: 'Guadeloupe', + value: 'gp', + }, + { + disabled: 'DISABLED', + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '671', + text: 'Guam', + value: 'gu', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '502', + text: 'Guatemala', + value: 'gt', + }, + { + disabled: 'DISABLED', + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '441481', + text: 'Guernsey', + value: 'gg', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '224', + text: 'Guinea', + value: 'gn', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '245', + text: 'Guinea-Bissau', + value: 'gw', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '592', + text: 'Guyana', + value: 'gy', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '509', + text: 'Haiti', + value: 'ht', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: {}, + is_country_supported: 0, + }, + }, + }, + text: 'Heard & McDonald Islands', + value: 'hm', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '504', + text: 'Honduras', + value: 'hn', + }, + { + disabled: 'DISABLED', + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '852', + text: 'Hong Kong SAR China', + value: 'hk', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_health_insurance_card: { + display_name: 'National Health Insurance Card', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + visa: { + display_name: 'Visa', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '36', + text: 'Hungary', + tin_format: ['^\\d{10}$'], + value: 'hu', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_health_insurance_card: { + display_name: 'National Health Insurance Card', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + visa: { + display_name: 'Visa', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '354', + text: 'Iceland', + tin_format: ['^\\d{10}$'], + value: 'is', + }, + { + identity: { + services: { + idv: { + documents_supported: { + aadhaar: { + additional: { + display_name: 'PAN Card', + format: '^[a-zA-Z]{5}\\d{4}[a-zA-Z]{1}$', + }, + display_name: 'Aadhaar Card', + format: '^[0-9]{12}$', + }, + drivers_license: { + display_name: 'Drivers License', + format: '^[a-zA-Z0-9]{10,17}$', + }, + epic: { + display_name: 'Voter ID', + format: '^[a-zA-Z]{3}[0-9]{7}$', + }, + pan: { + display_name: 'PAN Card', + format: '^[a-zA-Z]{5}\\d{4}[a-zA-Z]{1}$', + }, + passport: { + additional: { + display_name: 'File Number', + format: '^.{15}$', + }, + display_name: 'Passport', + format: '^.{8}$', + }, + }, + has_visual_sample: 0, + is_country_supported: 1, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + visa: { + display_name: 'Visa', + }, + voter_id: { + display_name: 'Voter Id', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '91', + text: 'India', + tin_format: ['^[a-zA-Z]{5}\\d{4}[a-zA-Z]$'], + value: 'in', + }, + { + identity: { + services: { + idv: { + documents_supported: { + nik: { + display_name: 'Nomor Induk Kependudukan', + format: '^\\d{16}$', + }, + }, + has_visual_sample: 0, + is_country_supported: 1, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '62', + text: 'Indonesia', + tin_format: ['^\\d{15}$'], + value: 'id', + }, + { + disabled: 'DISABLED', + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 0, + }, + }, + }, + phone_idd: '98', + text: 'Iran', + value: 'ir', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 0, + }, + }, + }, + phone_idd: '964', + text: 'Iraq', + value: 'iq', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_health_insurance_card: { + display_name: 'National Health Insurance Card', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + visa: { + display_name: 'Visa', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '353', + text: 'Ireland', + tin_format: ['^\\d{7}[A-Za-z]{1,2}$'], + value: 'ie', + }, + { + disabled: 'DISABLED', + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '44', + text: 'Isle of Man', + value: 'im', + }, + { + disabled: 'DISABLED', + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + passport: { + display_name: 'Passport', + }, + visa: { + display_name: 'Visa', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '972', + text: 'Israel', + value: 'il', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_health_insurance_card: { + display_name: 'National Health Insurance Card', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + visa: { + display_name: 'Visa', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '39', + text: 'Italy', + tin_format: [ + '^[A-Za-z]{6}\\d{2}[A-Za-z]\\d{2}[A-Za-z]\\d{3}[A-Za-z]$', + '^[A-Za-z]{6}\\d{2}[A-Za-z]\\d{2}[A-Za-z0-9]{4}[A-Za-z]$', + ], + value: 'it', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + passport: { + display_name: 'Passport', + }, + voter_id: { + display_name: 'Voter Id', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '1876', + text: 'Jamaica', + value: 'jm', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '81', + text: 'Japan', + value: 'jp', + }, + { + disabled: 'DISABLED', + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '441534', + text: 'Jersey', + value: 'je', + }, + { + disabled: 'DISABLED', + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '962', + text: 'Jordan', + value: 'jo', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '7', + text: 'Kazakhstan', + tin_format: ['^\\d{12}$'], + value: 'kz', + }, + { + identity: { + services: { + idv: { + documents_supported: { + alien_card: { + display_name: 'Alien Card', + format: '^[0-9]{6,9}$', + }, + national_id: { + display_name: 'National ID Number', + format: '^[0-9]{1,9}$', + }, + passport: { + display_name: 'Passport', + format: '^[A-Z0-9]{7,9}$', + }, + }, + has_visual_sample: 1, + is_country_supported: 1, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '254', + text: 'Kenya', + value: 'ke', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '686', + text: 'Kiribati', + value: 'ki', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '965', + text: 'Kuwait', + tin_format: ['^\\d{12}$'], + value: 'kw', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '996', + text: 'Kyrgyzstan', + value: 'kg', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '856', + text: 'Laos', + value: 'la', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_health_insurance_card: { + display_name: 'National Health Insurance Card', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + visa: { + display_name: 'Visa', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '371', + text: 'Latvia', + tin_format: ['^\\d{11}$', '^32\\d{9}$'], + value: 'lv', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '961', + text: 'Lebanon', + tin_format: ['^\\d+$'], + value: 'lb', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '266', + text: 'Lesotho', + value: 'ls', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '231', + text: 'Liberia', + value: 'lr', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 0, + }, + }, + }, + phone_idd: '218', + text: 'Libya', + value: 'ly', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_health_insurance_card: { + display_name: 'National Health Insurance Card', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + visa: { + display_name: 'Visa', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '417', + text: 'Liechtenstein', + tin_format: ['^\\d{1,12}$'], + value: 'li', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_health_insurance_card: { + display_name: 'National Health Insurance Card', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + visa: { + display_name: 'Visa', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '370', + text: 'Lithuania', + tin_format: ['^\\d{11}$'], + value: 'lt', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_health_insurance_card: { + display_name: 'National Health Insurance Card', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + visa: { + display_name: 'Visa', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '352', + text: 'Luxembourg', + tin_format: ['^\\d{13}$'], + value: 'lu', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '853', + text: 'Macau SAR China', + tin_format: ['^\\d+$'], + value: 'mo', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '389', + text: 'Macedonia', + value: 'mk', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '261', + text: 'Madagascar', + value: 'mg', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '265', + text: 'Malawi', + value: 'mw', + }, + { + disabled: 'DISABLED', + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + visa: { + display_name: 'Visa', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '60', + text: 'Malaysia', + value: 'my', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '960', + text: 'Maldives', + value: 'mv', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '223', + text: 'Mali', + value: 'ml', + }, + { + disabled: 'DISABLED', + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_health_insurance_card: { + display_name: 'National Health Insurance Card', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + visa: { + display_name: 'Visa', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '356', + text: 'Malta', + value: 'mt', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '692', + text: 'Marshall Islands', + tin_format: ['^\\d{7,8}$'], + value: 'mh', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: {}, + is_country_supported: 0, + }, + }, + }, + phone_idd: '596', + text: 'Martinique', + value: 'mq', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '222', + text: 'Mauritania', + value: 'mr', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '230', + text: 'Mauritius', + tin_format: ['^[17823]\\d{7}$'], + value: 'mu', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: {}, + is_country_supported: 0, + }, + }, + }, + phone_idd: '262269', + text: 'Mayotte', + value: 'yt', + }, + { + identity: { + services: { + idv: { + documents_supported: { + curp: { + display_name: 'Clave Única de Registro de Población', + format: '^[a-zA-Z]{4}[0-9]{6}[MmHh]{1}[a-zA-Z]{5}[a-zA-Z0-9]{2}$', + }, + }, + has_visual_sample: 0, + is_country_supported: 1, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + voter_id: { + display_name: 'Voter Id', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '52', + text: 'Mexico', + tin_format: ['^[a-zA-Z]{3,4}\\d{6}\\w{3}$'], + value: 'mx', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '691', + text: 'Micronesia', + value: 'fm', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '373', + text: 'Moldova', + value: 'md', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '377', + text: 'Monaco', + value: 'mc', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '976', + text: 'Mongolia', + value: 'mn', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '382', + text: 'Montenegro', + value: 'me', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '1664', + text: 'Montserrat', + value: 'ms', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '212', + text: 'Morocco', + value: 'ma', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '258', + text: 'Mozambique', + value: 'mz', + }, + { + disabled: 'DISABLED', + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '95', + text: 'Myanmar (Burma)', + value: 'mm', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '264', + text: 'Namibia', + value: 'na', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '674', + text: 'Nauru', + tin_format: ['^\\d{9}$'], + value: 'nr', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '977', + text: 'Nepal', + value: 'np', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + asylum_registration_card: { + display_name: 'Asylum Registration Card', + }, + driving_licence: { + display_name: 'Driving Licence', + }, + national_health_insurance_card: { + display_name: 'National Health Insurance Card', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + visa: { + display_name: 'Visa', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '31', + text: 'Netherlands', + tin_format: ['^\\d{9}$'], + value: 'nl', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: {}, + is_country_supported: 0, + }, + }, + }, + phone_idd: '687', + text: 'New Caledonia', + value: 'nc', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '64', + text: 'New Zealand', + tin_format: ['^\\d{8,9}$'], + value: 'nz', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '505', + text: 'Nicaragua', + value: 'ni', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '227', + text: 'Niger', + value: 'ne', + }, + { + identity: { + services: { + idv: { + documents_supported: { + drivers_license: { + display_name: 'Drivers License', + format: '^[a-zA-Z]{3}([ -]{1})?[A-Z0-9]{6,12}$', + }, + nin_slip: { + display_name: 'National ID Number Slip', + format: '^[0-9]{11}$', + }, + }, + has_visual_sample: 1, + is_country_supported: 1, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + voter_id: { + display_name: 'Voter Id', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '234', + text: 'Nigeria', + tin_format: ['^\\d{10}$', '^\\d{8}$', '^[A-Za-z]\\d{4,8}$', '^\\d{11}$', '^\\d{12}$'], + value: 'ng', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: {}, + is_country_supported: 0, + }, + }, + }, + phone_idd: '683', + text: 'Niue', + value: 'nu', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: {}, + is_country_supported: 0, + }, + }, + }, + phone_idd: '6723', + text: 'Norfolk Island', + value: 'nf', + }, + { + disabled: 'DISABLED', + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 0, + }, + }, + }, + phone_idd: '850', + text: 'North Korea', + value: 'kp', + }, + { + disabled: 'DISABLED', + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '1670', + text: 'Northern Mariana Islands', + value: 'mp', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + asylum_registration_card: { + display_name: 'Asylum Registration Card', + }, + driving_licence: { + display_name: 'Driving Licence', + }, + national_health_insurance_card: { + display_name: 'National Health Insurance Card', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + visa: { + display_name: 'Visa', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '47', + text: 'Norway', + value: 'no', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '968', + text: 'Oman', + value: 'om', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '92', + text: 'Pakistan', + tin_format: ['^\\d{13}$', '^\\d{7}$'], + value: 'pk', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '680', + text: 'Palau', + value: 'pw', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '970', + text: 'Palestinian Territories', + value: 'ps', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '507', + text: 'Panama', + tin_format: ['^[a-zA-Z0-9]\\d{7}$'], + value: 'pa', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '675', + text: 'Papua New Guinea', + value: 'pg', + }, + { + disabled: 'DISABLED', + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '595', + text: 'Paraguay', + value: 'py', + }, + { + identity: { + services: { + idv: { + documents_supported: { + national_id: { + display_name: 'National ID Number', + format: '^\\d{8}$', + }, + }, + has_visual_sample: 0, + is_country_supported: 1, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '51', + text: 'Peru', + value: 'pe', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_health_insurance_card: { + display_name: 'National Health Insurance Card', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + visa: { + display_name: 'Visa', + }, + voter_id: { + display_name: 'Voter Id', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '63', + text: 'Philippines', + value: 'ph', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: {}, + is_country_supported: 0, + }, + }, + }, + phone_idd: '649', + text: 'Pitcairn Islands', + value: 'pn', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_health_insurance_card: { + display_name: 'National Health Insurance Card', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + visa: { + display_name: 'Visa', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '48', + text: 'Poland', + tin_format: ['^\\d{10,11}$'], + value: 'pl', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_health_insurance_card: { + display_name: 'National Health Insurance Card', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + visa: { + display_name: 'Visa', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '351', + text: 'Portugal', + tin_format: ['^\\d{9}$'], + value: 'pt', + }, + { + disabled: 'DISABLED', + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '1787', + text: 'Puerto Rico', + value: 'pr', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '974', + text: 'Qatar', + value: 'qa', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: {}, + is_country_supported: 0, + }, + }, + }, + phone_idd: '262', + text: 'Reunion', + value: 're', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_health_insurance_card: { + display_name: 'National Health Insurance Card', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '40', + text: 'Romania', + tin_format: ['^\\d{13}$'], + value: 'ro', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 0, + }, + }, + }, + phone_idd: '7', + text: 'Russia', + tin_format: ['^\\d{12}$'], + value: 'ru', + }, + { + disabled: 'DISABLED', + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '250', + text: 'Rwanda', + value: 'rw', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '590', + text: 'Saint Barthelemy', + value: 'bl', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 0, + }, + }, + }, + phone_idd: '290', + text: 'Saint Helena', + value: 'sh', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '1869', + text: 'Saint Kitts and Nevis', + value: 'kn', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '1758', + text: 'Saint Lucia', + tin_format: ['^\\d{1,6}$'], + value: 'lc', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '590', + text: 'Saint Martin', + value: 'mf', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: {}, + is_country_supported: 0, + }, + }, + }, + phone_idd: '508', + text: 'Saint Pierre and Miquelon', + value: 'pm', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '685', + text: 'Samoa', + value: 'ws', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '378', + text: 'San Marino', + tin_format: ['^\\d{8,9}$', '^[SsMm]\\d{5}$'], + value: 'sm', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '239', + text: 'Sao Tome and Principe', + value: 'st', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '966', + text: 'Saudi Arabia', + tin_format: ['^\\d{10}$'], + value: 'sa', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '221', + text: 'Senegal', + value: 'sn', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '381', + text: 'Serbia', + value: 'rs', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '248', + text: 'Seychelles', + tin_format: ['^\\d{2}[1-7]\\d{6}$'], + value: 'sc', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '232', + text: 'Sierra Leone', + value: 'sl', + }, + { + disabled: 'DISABLED', + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '65', + text: 'Singapore', + tin_format: [ + '^[SsTtFfGg]\\d{7}[A-Za-z]$', + '^[A-Za-z]{9,10}$', + '^[Ff]0000\\d{6}$', + '^[Ff]\\d{9}$', + '^([Ss]|[Tt][4])\\d{9}$', + '^[Aa]\\d{9}$', + ], + value: 'sg', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + }, + is_country_supported: 0, + }, + }, + }, + phone_idd: '1721', + text: 'Sint Maarten', + value: 'sx', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_health_insurance_card: { + display_name: 'National Health Insurance Card', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + visa: { + display_name: 'Visa', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '421', + text: 'Slovakia', + tin_format: ['^\\d{9,10}$'], + value: 'sk', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_health_insurance_card: { + display_name: 'National Health Insurance Card', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + visa: { + display_name: 'Visa', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '386', + text: 'Slovenia', + tin_format: ['^\\d{8}$'], + value: 'si', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '677', + text: 'Solomon Islands', + value: 'sb', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '252', + text: 'Somalia', + value: 'so', + }, + { + identity: { + services: { + idv: { + documents_supported: { + national_id: { + display_name: 'National ID Number', + format: '^[0-9]{13}$', + }, + }, + has_visual_sample: 1, + is_country_supported: 1, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '27', + text: 'South Africa', + tin_format: ['^[01239]\\d{3}\\/?\\d{3}\\/?\\d{3}$'], + value: 'za', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: {}, + is_country_supported: 0, + }, + }, + }, + phone_idd: '500', + text: 'South Georgia & South Sandwich Islands', + value: 'gs', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '82', + text: 'South Korea', + tin_format: ['^\\d{13}$'], + value: 'kr', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '211', + text: 'South Sudan', + value: 'ss', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_health_insurance_card: { + display_name: 'National Health Insurance Card', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + visa: { + display_name: 'Visa', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '34', + text: 'Spain', + tin_format: ['^[KkLlMmXxYyZz]\\d{7}[A-Za-z]$', '^\\d{8}[A-Za-z]$'], + value: 'es', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '94', + text: 'Sri Lanka', + value: 'lk', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '1784', + text: 'St. Vincent & Grenadines', + tin_format: ['^\\d+$'], + value: 'vc', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '249', + text: 'Sudan', + value: 'sd', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '597', + text: 'Suriname', + value: 'sr', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: {}, + is_country_supported: 0, + }, + }, + }, + phone_idd: '4779', + text: 'Svalbard and Jan Mayen', + value: 'sj', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '268', + text: 'Swaziland', + value: 'sz', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_health_insurance_card: { + display_name: 'National Health Insurance Card', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + visa: { + display_name: 'Visa', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '46', + text: 'Sweden', + tin_format: ['^\\d{10}$', '^\\d{12}$'], + value: 'se', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_health_insurance_card: { + display_name: 'National Health Insurance Card', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + visa: { + display_name: 'Visa', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '41', + text: 'Switzerland', + tin_format: [ + '^756\\.[0-9]{4}\\.[0-9]{4}\\.[0-9]{2}$', + '^(CHE|che)-[0-9]{3}\\.[0-9]{3}\\.[0-9]{3}$', + ], + value: 'ch', + }, + { + disabled: 'DISABLED', + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 0, + }, + }, + }, + phone_idd: '963', + text: 'Syria', + value: 'sy', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '886', + text: 'Taiwan', + value: 'tw', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '992', + text: 'Tajikistan', + value: 'tj', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + voter_id: { + display_name: 'Voter Id', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '255', + text: 'Tanzania', + value: 'tz', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '66', + text: 'Thailand', + value: 'th', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + }, + is_country_supported: 0, + }, + }, + }, + phone_idd: '670', + text: 'Timor-Leste', + value: 'tl', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '228', + text: 'Togo', + value: 'tg', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: {}, + is_country_supported: 0, + }, + }, + }, + phone_idd: '690', + text: 'Tokelau', + value: 'tk', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '676', + text: 'Tonga', + value: 'to', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '1868', + text: 'Trinidad and Tobago', + value: 'tt', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '216', + text: 'Tunisia', + value: 'tn', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '90', + text: 'Turkey', + tin_format: ['^\\d{10}$'], + value: 'tr', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '993', + text: 'Turkmenistan', + value: 'tm', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '1649', + text: 'Turks and Caicos Islands', + value: 'tc', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '688', + text: 'Tuvalu', + value: 'tv', + }, + { + disabled: 'DISABLED', + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: {}, + is_country_supported: 0, + }, + }, + }, + text: 'U.S. Outlying Islands', + value: 'um', + }, + { + disabled: 'DISABLED', + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '1340', + text: 'U.S. Virgin Islands', + value: 'vi', + }, + { + identity: { + services: { + idv: { + documents_supported: { + national_id_no_photo: { + additional: { + display_name: 'Card Number', + format: '^[a-zA-Z0-9]+$', + }, + display_name: 'National ID Number', + format: '^[a-zA-Z0-9]{14}$', + }, + }, + has_visual_sample: 1, + is_country_supported: 1, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '256', + text: 'Uganda', + value: 'ug', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + visa: { + display_name: 'Visa', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '380', + text: 'Ukraine', + value: 'ua', + }, + { + disabled: 'DISABLED', + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '971', + text: 'United Arab Emirates', + value: 'ae', + }, + { + disabled: 'DISABLED', + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + asylum_registration_card: { + display_name: 'Asylum Registration Card', + }, + driving_licence: { + display_name: 'Driving Licence', + }, + immigration_status_document: { + display_name: 'Immigration Status Document', + }, + national_health_insurance_card: { + display_name: 'National Health Insurance Card', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + visa: { + display_name: 'Visa', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '44', + text: 'United Kingdom', + tin_format: ['^\\d{10}$', '^[A-Za-z]{2}\\d{6}[A-Za-z]$'], + value: 'gb', + }, + { + disabled: 'DISABLED', + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + visa: { + display_name: 'Visa', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '1', + text: 'United States', + value: 'us', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '598', + text: 'Uruguay', + tin_format: ['^\\d{8,9}$', '^\\d{8}001\\d$'], + value: 'uy', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '998', + text: 'Uzbekistan', + value: 'uz', + }, + { + disabled: 'DISABLED', + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '678', + text: 'Vanuatu', + value: 'vu', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '379', + text: 'Vatican City', + value: 'va', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '58', + text: 'Venezuela', + value: 've', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + residence_permit: { + display_name: 'Residence Permit', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '84', + text: 'Vietnam', + value: 'vn', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: {}, + is_country_supported: 0, + }, + }, + }, + phone_idd: '681', + text: 'Wallis and Futuna', + value: 'wf', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: {}, + is_country_supported: 0, + }, + }, + }, + phone_idd: '21', + text: 'Western Sahara', + value: 'eh', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '967', + text: 'Yemen', + value: 'ye', + }, + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '260', + text: 'Zambia', + value: 'zm', + }, + { + identity: { + services: { + idv: { + documents_supported: { + national_id: { + display_name: 'National ID Number', + format: '^[0-9]{8,9}[a-zA-Z]{1}[0-9]{2}$', + }, + }, + has_visual_sample: 1, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '263', + text: 'Zimbabwe', + value: 'zw', + }, + ], + }; + } +} + +export default mock_residents_list; diff --git a/packages/integration/src/mocks/location/states_list.ts b/packages/integration/src/mocks/location/states_list.ts new file mode 100644 index 000000000000..5c63d417790f --- /dev/null +++ b/packages/integration/src/mocks/location/states_list.ts @@ -0,0 +1,326 @@ +import { Context } from 'Utils/mocks/mocks'; + +function mock_states_list(context: Context) { + if ('states_list' in context.request) { + context.response = { + echo_req: { + req_id: context.req_id, + states_list: 'th', + }, + msg_type: 'states_list', + req_id: context.req_id, + states_list: [ + { + text: 'Amnat Charoen', + value: '37', + }, + { + text: 'Ang Thong', + value: '15', + }, + { + text: 'Buri Ram', + value: '31', + }, + { + text: 'Chachoengsao', + value: '24', + }, + { + text: 'Chai Nat', + value: '18', + }, + { + text: 'Chaiyaphum', + value: '36', + }, + { + text: 'Chanthaburi', + value: '22', + }, + { + text: 'Chiang Mai', + value: '50', + }, + { + text: 'Chiang Rai', + value: '57', + }, + { + text: 'Chon Buri', + value: '20', + }, + { + text: 'Chumphon', + value: '86', + }, + { + text: 'Kalasin', + value: '46', + }, + { + text: 'Kamphaeng Phet', + value: '62', + }, + { + text: 'Kanchanaburi', + value: '71', + }, + { + text: 'Khon Kaen', + value: '40', + }, + { + text: 'Krabi', + value: '81', + }, + { + text: 'Krung Thep Maha Nakhon Bangkok', + value: '10', + }, + { + text: 'Lampang', + value: '52', + }, + { + text: 'Lamphun', + value: '51', + }, + { + text: 'Loei', + value: '42', + }, + { + text: 'Lop Buri', + value: '16', + }, + { + text: 'Mae Hong Son', + value: '58', + }, + { + text: 'Maha Sarakham', + value: '44', + }, + { + text: 'Mukdahan', + value: '49', + }, + { + text: 'Nakhon Nayok', + value: '26', + }, + { + text: 'Nakhon Pathom', + value: '73', + }, + { + text: 'Nakhon Phanom', + value: '48', + }, + { + text: 'Nakhon Ratchasima', + value: '30', + }, + { + text: 'Nakhon Sawan', + value: '60', + }, + { + text: 'Nakhon Si Thammarat', + value: '80', + }, + { + text: 'Nan', + value: '55', + }, + { + text: 'Narathiwat', + value: '96', + }, + { + text: 'Nong Bua Lam Phu', + value: '39', + }, + { + text: 'Nong Khai', + value: '43', + }, + { + text: 'Nonthaburi', + value: '12', + }, + { + text: 'Pathum Thani', + value: '13', + }, + { + text: 'Pattani', + value: '94', + }, + { + text: 'Phangnga', + value: '82', + }, + { + text: 'Phatthalung', + value: '93', + }, + { + text: 'Phatthaya', + value: 'S', + }, + { + text: 'Phayao', + value: '56', + }, + { + text: 'Phetchabun', + value: '67', + }, + { + text: 'Phetchaburi', + value: '76', + }, + { + text: 'Phichit', + value: '66', + }, + { + text: 'Phitsanulok', + value: '65', + }, + { + text: 'Phra Nakhon Si Ayutthaya', + value: '14', + }, + { + text: 'Phrae', + value: '54', + }, + { + text: 'Phuket', + value: '83', + }, + { + text: 'Prachin Buri', + value: '25', + }, + { + text: 'Prachuap Khiri Khan', + value: '77', + }, + { + text: 'Ranong', + value: '85', + }, + { + text: 'Ratchaburi', + value: '70', + }, + { + text: 'Rayong', + value: '21', + }, + { + text: 'Roi Et', + value: '45', + }, + { + text: 'Sa Kaeo', + value: '27', + }, + { + text: 'Sakon Nakhon', + value: '47', + }, + { + text: 'Samut Prakan', + value: '11', + }, + { + text: 'Samut Sakhon', + value: '74', + }, + { + text: 'Samut Songkhram', + value: '75', + }, + { + text: 'Saraburi', + value: '19', + }, + { + text: 'Satun', + value: '91', + }, + { + text: 'Si Sa Ket', + value: '33', + }, + { + text: 'Sing Buri', + value: '17', + }, + { + text: 'Songkhla', + value: '90', + }, + { + text: 'Sukhothai', + value: '64', + }, + { + text: 'Suphan Buri', + value: '72', + }, + { + text: 'Surat Thani', + value: '84', + }, + { + text: 'Surin', + value: '32', + }, + { + text: 'Tak', + value: '63', + }, + { + text: 'Trang', + value: '92', + }, + { + text: 'Trat', + value: '23', + }, + { + text: 'Ubon Ratchathani', + value: '34', + }, + { + text: 'Udon Thani', + value: '41', + }, + { + text: 'Uthai Thani', + value: '61', + }, + { + text: 'Uttaradit', + value: '53', + }, + { + text: 'Yala', + value: '95', + }, + { + text: 'Yasothon', + value: '35', + }, + ], + }; + } +} + +export default mock_states_list; diff --git a/packages/integration/src/utils/mocks/mocks.ts b/packages/integration/src/utils/mocks/mocks.ts new file mode 100644 index 000000000000..8757facaa27b --- /dev/null +++ b/packages/integration/src/utils/mocks/mocks.ts @@ -0,0 +1,576 @@ +import * as https from 'https'; +import { EventEmitter } from 'events'; +import { Server as WsServer } from 'ws'; +import { generate } from 'selfsigned'; +import { Page, expect } from '@playwright/test'; + +import type { + APITokenRequest, + APITokenResponse, + AccountLimitsRequest, + AccountLimitsResponse, + AccountStatusRequest, + AccountStatusResponse, + ActiveSymbolsRequest, + ActiveSymbolsResponse, + ApplicationDeleteRequest, + ApplicationDeleteResponse, + ApplicationGetDetailsRequest, + ApplicationGetDetailsResponse, + ApplicationListRequest, + ApplicationListResponse, + ApplicationMarkupDetailsRequest, + ApplicationMarkupDetailsResponse, + ApplicationMarkupStatisticsRequest, + ApplicationMarkupStatisticsResponse, + ApplicationRegisterRequest, + ApplicationRegisterResponse, + ApplicationUpdateRequest, + ApplicationUpdateResponse, + AssetIndexRequest, + AssetIndexResponse, + AuthorizeRequest, + AuthorizeResponse, + BalanceRequest, + BalanceResponse, + BuyContractForMultipleAccountsRequest, + BuyContractForMultipleAccountsResponse, + BuyContractRequest, + BuyContractResponse, + CancelAContractRequest, + CancelAContractResponse, + CashierInformationRequest, + CashierInformationResponse, + ContractsForSymbolRequest, + ContractsForSymbolResponse, + CopyTradingListRequest, + CopyTradingListResponse, + CopyTradingStartRequest, + CopyTradingStartResponse, + CopyTradingStatisticsRequest, + CopyTradingStatisticsResponse, + CopyTradingStopRequest, + CopyTradingStopResponse, + CountriesListRequest, + CountriesListResponse, + CryptocurrencyConfigurationsRequest, + CryptocurrencyConfigurationsResponse, + DocumentUploadRequest, + DocumentUploadResponse, + EconomicCalendarRequest, + EconomicCalendarResponse, + ExchangeRatesRequest, + ExchangeRatesResponse, + ForgetAllRequest, + ForgetAllResponse, + ForgetRequest, + ForgetResponse, + GetAccountSettingsRequest, + GetAccountSettingsResponse, + GetFinancialAssessmentRequest, + GetFinancialAssessmentResponse, + GetSelfExclusionRequest, + GetSelfExclusionResponse, + IdentityVerificationAddDocumentRequest, + IdentityVerificationAddDocumentResponse, + LandingCompanyDetailsRequest, + LandingCompanyDetailsResponse, + LandingCompanyRequest, + LandingCompanyResponse, + LogOutRequest, + LogOutResponse, + LoginHistoryRequest, + LoginHistoryResponse, + MT5AccountsListRequest, + MT5AccountsListResponse, + MT5DepositRequest, + MT5DepositResponse, + MT5GetSettingRequest, + MT5GetSettingResponse, + MT5NewAccountRequest, + MT5NewAccountResponse, + MT5PasswordChangeRequest, + MT5PasswordChangeResponse, + MT5PasswordCheckRequest, + MT5PasswordCheckResponse, + MT5PasswordResetRequest, + MT5PasswordResetResponse, + MT5WithdrawalRequest, + MT5WithdrawalResponse, + NewRealMoneyAccountDefaultLandingCompanyRequest, + NewRealMoneyAccountDefaultLandingCompanyResponse, + NewRealMoneyAccountDerivInvestmentEuropeLtdRequest, + NewRealMoneyAccountDerivInvestmentEuropeLtdResponse, + NewVirtualMoneyAccountRequest, + NewVirtualMoneyAccountResponse, + OAuthApplicationsRequest, + OAuthApplicationsResponse, + P2PAdvertCreateRequest, + P2PAdvertCreateResponse, + P2PAdvertInformationRequest, + P2PAdvertInformationResponse, + P2PAdvertListRequest, + P2PAdvertListResponse, + P2PAdvertUpdateRequest, + P2PAdvertUpdateResponse, + P2PAdvertiserAdvertsRequest, + P2PAdvertiserAdvertsResponse, + P2PAdvertiserCreateRequest, + P2PAdvertiserCreateResponse, + P2PAdvertiserInformationRequest, + P2PAdvertiserInformationResponse, + P2PAdvertiserListRequest, + P2PAdvertiserListResponse, + P2PAdvertiserPaymentMethodsRequest, + P2PAdvertiserPaymentMethodsResponse, + P2PAdvertiserRelationsRequest, + P2PAdvertiserRelationsResponse, + P2PAdvertiserUpdateRequest, + P2PAdvertiserUpdateResponse, + P2PChatCreateRequest, + P2PChatCreateResponse, + P2POrderCancelRequest, + P2POrderCancelResponse, + P2POrderConfirmRequest, + P2POrderConfirmResponse, + P2POrderCreateRequest, + P2POrderCreateResponse, + P2POrderDisputeRequest, + P2POrderDisputeResponse, + P2POrderInformationRequest, + P2POrderInformationResponse, + P2POrderListRequest, + P2POrderListResponse, + P2POrderReviewRequest, + P2POrderReviewResponse, + P2PPaymentMethodsRequest, + P2PPaymentMethodsResponse, + P2PPingRequest, + P2PPingResponse, + PaymentAgentCreateRequest, + PaymentAgentCreateResponse, + PaymentAgentDetailsRequest, + PaymentAgentDetailsResponse, + PaymentAgentListRequest, + PaymentAgentListResponse, + PaymentAgentTransferRequest, + PaymentAgentTransferResponse, + PaymentAgentWithdrawJustificationRequest, + PaymentAgentWithdrawJustificationResponse, + PaymentAgentWithdrawRequest, + PaymentAgentWithdrawResponse, + PaymentMethodsRequest, + PaymentMethodsResponse, + PayoutCurrenciesRequest, + PayoutCurrenciesResponse, + PingRequest, + PingResponse, + PortfolioRequest, + PortfolioResponse, + PriceProposalOpenContractsRequest, + PriceProposalOpenContractsResponse, + PriceProposalRequest, + PriceProposalResponse, + ProfitTableRequest, + ProfitTableResponse, + RealityCheckRequest, + RealityCheckResponse, + RevokeOauthApplicationRequest, + RevokeOauthApplicationResponse, + SellContractRequest, + SellContractResponse, + SellContractsMultipleAccountsRequest, + SellContractsMultipleAccountsResponse, + SellExpiredContractsRequest, + SellExpiredContractsResponse, + ServerListRequest, + ServerListResponse, + ServerStatusRequest, + ServerStatusResponse, + ServerTimeRequest, + ServerTimeResponse, + SetAccountCurrencyRequest, + SetAccountCurrencyResponse, + SetAccountSettingsRequest, + SetAccountSettingsResponse, + SetFinancialAssessmentRequest, + SetFinancialAssessmentResponse, + SetSelfExclusionRequest, + SetSelfExclusionResponse, + StatementRequest, + StatementResponse, + StatesListRequest, + StatesListResponse, + TermsAndConditionsApprovalRequest, + TermsAndConditionsApprovalResponse, + TicksHistoryRequest, + TicksHistoryResponse, + TicksStreamRequest, + TicksStreamResponse, + TopUpVirtualMoneyAccountRequest, + TopUpVirtualMoneyAccountResponse, + TradingDurationsRequest, + TradingDurationsResponse, + TradingPlatformInvestorPasswordResetRequest, + TradingPlatformInvestorPasswordResetResponse, + TradingPlatformPasswordResetRequest, + TradingPlatformPasswordResetResponse, + TradingTimesRequest, + TradingTimesResponse, + TransactionsStreamRequest, + TransactionsStreamResponse, + TransferBetweenAccountsRequest, + TransferBetweenAccountsResponse, + UnsubscribeEmailRequest, + UnsubscribeEmailResponse, + UpdateContractHistoryRequest, + UpdateContractHistoryResponse, + UpdateContractRequest, + UpdateContractResponse, + VerifyEmailCellxpertRequest, + VerifyEmailCellxpertResponse, + VerifyEmailRequest, + VerifyEmailResponse, +} from '@deriv/api-types'; + +type Request = + | APITokenRequest + | AccountLimitsRequest + | AccountStatusRequest + | ActiveSymbolsRequest + | ApplicationDeleteRequest + | ApplicationGetDetailsRequest + | ApplicationListRequest + | ApplicationMarkupDetailsRequest + | ApplicationMarkupStatisticsRequest + | ApplicationRegisterRequest + | ApplicationUpdateRequest + | AssetIndexRequest + | AuthorizeRequest + | BalanceRequest + | BuyContractForMultipleAccountsRequest + | BuyContractRequest + | CancelAContractRequest + | CashierInformationRequest + | ContractsForSymbolRequest + | CopyTradingListRequest + | CopyTradingStartRequest + | CopyTradingStatisticsRequest + | CopyTradingStopRequest + | CountriesListRequest + | CryptocurrencyConfigurationsRequest + | DocumentUploadRequest + | EconomicCalendarRequest + | ExchangeRatesRequest + | ForgetAllRequest + | ForgetRequest + | GetAccountSettingsRequest + | GetFinancialAssessmentRequest + | GetSelfExclusionRequest + | IdentityVerificationAddDocumentRequest + | LandingCompanyDetailsRequest + | LandingCompanyRequest + | LogOutRequest + | LoginHistoryRequest + | MT5AccountsListRequest + | MT5DepositRequest + | MT5GetSettingRequest + | MT5NewAccountRequest + | MT5PasswordChangeRequest + | MT5PasswordCheckRequest + | MT5PasswordResetRequest + | MT5WithdrawalRequest + | NewRealMoneyAccountDefaultLandingCompanyRequest + | NewRealMoneyAccountDerivInvestmentEuropeLtdRequest + | NewVirtualMoneyAccountRequest + | OAuthApplicationsRequest + | P2PAdvertCreateRequest + | P2PAdvertInformationRequest + | P2PAdvertListRequest + | P2PAdvertUpdateRequest + | P2PAdvertiserAdvertsRequest + | P2PAdvertiserCreateRequest + | P2PAdvertiserInformationRequest + | P2PAdvertiserListRequest + | P2PAdvertiserPaymentMethodsRequest + | P2PAdvertiserRelationsRequest + | P2PAdvertiserUpdateRequest + | P2PChatCreateRequest + | P2POrderCancelRequest + | P2POrderConfirmRequest + | P2POrderCreateRequest + | P2POrderDisputeRequest + | P2POrderInformationRequest + | P2POrderListRequest + | P2POrderReviewRequest + | P2PPaymentMethodsRequest + | P2PPingRequest + | PaymentAgentCreateRequest + | PaymentAgentDetailsRequest + | PaymentAgentListRequest + | PaymentAgentTransferRequest + | PaymentAgentWithdrawJustificationRequest + | PaymentAgentWithdrawRequest + | PaymentMethodsRequest + | PayoutCurrenciesRequest + | PingRequest + | PortfolioRequest + | PriceProposalOpenContractsRequest + | PriceProposalRequest + | ProfitTableRequest + | RealityCheckRequest + | RevokeOauthApplicationRequest + | SellContractRequest + | SellContractsMultipleAccountsRequest + | SellExpiredContractsRequest + | ServerListRequest + | ServerStatusRequest + | ServerTimeRequest + | SetAccountCurrencyRequest + | SetAccountSettingsRequest + | SetFinancialAssessmentRequest + | SetSelfExclusionRequest + | StatementRequest + | StatesListRequest + | TermsAndConditionsApprovalRequest + | TicksHistoryRequest + | TicksStreamRequest + | TopUpVirtualMoneyAccountRequest + | TradingDurationsRequest + | TradingPlatformInvestorPasswordResetRequest + | TradingPlatformPasswordResetRequest + | TradingTimesRequest + | TransactionsStreamRequest + | TransferBetweenAccountsRequest + | UnsubscribeEmailRequest + | UpdateContractHistoryRequest + | UpdateContractRequest + | VerifyEmailCellxpertRequest + | VerifyEmailRequest; + +type Response = + | APITokenResponse + | AccountLimitsResponse + | AccountStatusResponse + | ActiveSymbolsResponse + | ApplicationDeleteResponse + | ApplicationGetDetailsResponse + | ApplicationListResponse + | ApplicationMarkupDetailsResponse + | ApplicationMarkupStatisticsResponse + | ApplicationRegisterResponse + | ApplicationUpdateResponse + | AssetIndexResponse + | AuthorizeResponse + | BalanceResponse + | BuyContractForMultipleAccountsResponse + | BuyContractResponse + | CancelAContractResponse + | CashierInformationResponse + | ContractsForSymbolResponse + | CopyTradingListResponse + | CopyTradingStartResponse + | CopyTradingStatisticsResponse + | CopyTradingStopResponse + | CountriesListResponse + | CryptocurrencyConfigurationsResponse + | DocumentUploadResponse + | EconomicCalendarResponse + | ExchangeRatesResponse + | ForgetAllResponse + | ForgetResponse + | GetAccountSettingsResponse + | GetFinancialAssessmentResponse + | GetSelfExclusionResponse + | IdentityVerificationAddDocumentResponse + | LandingCompanyDetailsResponse + | LandingCompanyResponse + | LogOutResponse + | LoginHistoryResponse + | MT5AccountsListResponse + | MT5DepositResponse + | MT5GetSettingResponse + | MT5NewAccountResponse + | MT5PasswordChangeResponse + | MT5PasswordCheckResponse + | MT5PasswordResetResponse + | MT5WithdrawalResponse + | NewRealMoneyAccountDefaultLandingCompanyResponse + | NewRealMoneyAccountDerivInvestmentEuropeLtdResponse + | NewVirtualMoneyAccountResponse + | OAuthApplicationsResponse + | P2PAdvertCreateResponse + | P2PAdvertInformationResponse + | P2PAdvertListResponse + | P2PAdvertUpdateResponse + | P2PAdvertiserAdvertsResponse + | P2PAdvertiserCreateResponse + | P2PAdvertiserInformationResponse + | P2PAdvertiserListResponse + | P2PAdvertiserPaymentMethodsResponse + | P2PAdvertiserRelationsResponse + | P2PAdvertiserUpdateResponse + | P2PChatCreateResponse + | P2POrderCancelResponse + | P2POrderConfirmResponse + | P2POrderCreateResponse + | P2POrderDisputeResponse + | P2POrderInformationResponse + | P2POrderListResponse + | P2POrderReviewResponse + | P2PPaymentMethodsResponse + | P2PPingResponse + | PaymentAgentCreateResponse + | PaymentAgentDetailsResponse + | PaymentAgentListResponse + | PaymentAgentTransferResponse + | PaymentAgentWithdrawJustificationResponse + | PaymentAgentWithdrawResponse + | PaymentMethodsResponse + | PayoutCurrenciesResponse + | PingResponse + | PortfolioResponse + | PriceProposalOpenContractsResponse + | PriceProposalResponse + | ProfitTableResponse + | RealityCheckResponse + | RevokeOauthApplicationResponse + | SellContractResponse + | SellContractsMultipleAccountsResponse + | SellExpiredContractsResponse + | ServerListResponse + | ServerStatusResponse + | ServerTimeResponse + | SetAccountCurrencyResponse + | SetAccountSettingsResponse + | SetFinancialAssessmentResponse + | SetSelfExclusionResponse + | StatementResponse + | StatesListResponse + | TermsAndConditionsApprovalResponse + | TicksHistoryResponse + | TicksStreamResponse + | TopUpVirtualMoneyAccountResponse + | TradingDurationsResponse + | TradingPlatformInvestorPasswordResetResponse + | TradingPlatformPasswordResetResponse + | TradingTimesResponse + | TransactionsStreamResponse + | TransferBetweenAccountsResponse + | UnsubscribeEmailResponse + | UpdateContractHistoryResponse + | UpdateContractResponse + | VerifyEmailCellxpertResponse + | VerifyEmailResponse; + +export interface Context { + request: Request; + response?: Response; + req_id: number; + socket: any; +} + +export interface MockServer extends EventEmitter { + close: () => void; + url: string; + add: (mock: (context: Context) => void) => void; + remove: (mock: (context: Context) => void) => void; +} + +type Options = { + port?: number; +}; + +const pems = generate(); + +export async function createMockServer( + mocks: Array<(context: Context) => void>, + options?: Options +): Promise { + const eventEmitter = new EventEmitter() as MockServer; + + const server = https.createServer({ key: pems.private, cert: pems.cert }); + await new Promise(resolve => server.listen(options?.port ?? 0, resolve)); + const wss = new WsServer({ server }); + + wss.on('connection', ws => { + ws.on('message', async message => { + const parsedMessage = JSON.parse(message.toString()); + const context: Context = { + request: parsedMessage, + req_id: parsedMessage.req_id, + socket: ws, + }; + + mocks.flat().forEach(mock => mock(context)); + + if (context.response) { + ws.send(JSON.stringify(context.response)); + } + + if (!context.response) { + /* eslint no-console: 0 */ + console.warn('mock websocket request was not handled for', context.request); + } + }); + }); + + eventEmitter.close = () => { + wss.close(); + server.close(); + }; + + const address = server.address() as { port: number }; + eventEmitter.url = `localhost:${address.port}`; + + eventEmitter.add = mock => { + mocks.push(mock); + }; + + eventEmitter.remove = mockToRemove => { + const index = mocks.findIndex(mock => mock === mockToRemove); + if (index !== -1) { + mocks.splice(index, 1); + } + }; + + return eventEmitter; +} + +type SetupMocksOptions = { + baseURL?: string; + page: Page; + mocks: Array<(context: Context) => void>; +}; + +async function setupMocks({ baseURL, page, mocks }: SetupMocksOptions) { + if (!baseURL) { + throw new Error('baseURL is required'); + } + + const mockServer = await createMockServer(mocks); + page.addListener('close', () => { + mockServer.close(); + }); + await page.goto(baseURL, { waitUntil: 'commit' }); + + await page.evaluate(server_url => { + window.localStorage.setItem('config.server_url', server_url); + }, mockServer.url); + + await page.goto( + `${baseURL}/?acct1=CR5712715&token1=a1-x0000000000000000000000000001&cur1=USD&acct2=CR5712710&token2=a1-x0000000000000000000000000002&cur2=BTC&acct3=VRTC8420051&token3=a1-x0000000000000000000000000003&cur3=USD&state=` + ); + + await expect + .poll(async () => { + return page.evaluate(() => { + return window.localStorage.getItem('active_loginid'); + }); + }) + .toBe('CR5712715'); + + return mockServer; +} + +export default setupMocks; diff --git a/end-to-end-test/utils/suspend/suspend.ts b/packages/integration/src/utils/suspend/suspend.ts similarity index 100% rename from end-to-end-test/utils/suspend/suspend.ts rename to packages/integration/src/utils/suspend/suspend.ts diff --git a/packages/integration/tsconfig.json b/packages/integration/tsconfig.json new file mode 100644 index 000000000000..f0475c06529d --- /dev/null +++ b/packages/integration/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "paths": { + "Mocks/*": ["src/mocks/*"], + "Utils/*": ["src/utils/*"] + }, + "baseUrl": "./" + }, + "include": ["src"] +} diff --git a/packages/p2p/crowdin/messages.json b/packages/p2p/crowdin/messages.json index 6e301317b3da..7658f91acb26 100644 --- a/packages/p2p/crowdin/messages.json +++ b/packages/p2p/crowdin/messages.json @@ -1 +1 @@ -{"6794664":"Ads that match your Deriv P2P balance and limit.","19789721":"Nobody has blocked you. Yay!","24711354":"Total orders <0>30d | <1>lifetime","47573834":"Fixed rate (1 {{account_currency}})","50672601":"Bought","51881712":"You already have an ad with the same exchange rate for this currency pair and order type.

Please set a different rate for your ad.","55916349":"All","68867477":"Order ID {{ id }}","81450871":"We couldn’t find that page","121738739":"Send","122280248":"Avg release time <0>30d","134205943":"Your ads with fixed rates have been deactivated. Set floating rates to reactivate them.","140800401":"Float","145959105":"Choose a nickname","150156106":"Save changes","159757877":"You won't see {{advertiser_name}}'s ads anymore and they won't be able to place orders on your ads.","170072126":"Seen {{ duration }} days ago","173939998":"Avg. pay time <0>30d","197477687":"Edit {{ad_type}} ad","203271702":"Try again","231473252":"Preferred currency","233677840":"of the market rate","246815378":"Once set, your nickname cannot be changed.","276261353":"Avg pay time <0>30d","277542386":"Please use <0>live chat to contact our Customer Support team for help.","316725580":"You can no longer rate this transaction.","323002325":"Post ad","324970564":"Seller's contact details","338910048":"You will appear to other users as","358133589":"Unblock {{advertiser_name}}?","364681129":"Contact details","367579676":"Blocked","392469164":"You have blocked {{advertiser_name}}.","407600801":"Have you paid {{amount}} {{currency}} to {{other_user_name}}?","416167062":"You'll receive","424668491":"expired","439264204":"Please set a different minimum and/or maximum order limit.

The range of your ad should not overlap with any of your active ads.","452752527":"Rate (1 {{ currency }})","460477293":"Enter message","464044457":"Buyer's nickname","473688701":"Enter a valid amount","476023405":"Didn't receive the email?","488150742":"Resend email","498500965":"Seller's nickname","500514593":"Hide my ads","501523417":"You have no orders.","517202770":"Set fixed rate","523301614":"Release {{amount}} {{currency}}","525380157":"Buy {{offered_currency}} order","531912261":"Bank name, account number, beneficiary name","554135844":"Edit","555447610":"You won't be able to change your buy and sell limits again after this. Do you want to continue?","560402954":"User rating","565060416":"Exchange rate","580715136":"Please register with us!","587882987":"Advertisers","611376642":"Clear","612069973":"Would you recommend this buyer?","628581263":"The {{local_currency}} market rate has changed.","649549724":"I’ve not received any payment.","654193846":"The verification link appears to be invalid. Hit the button below to request for a new one","661808069":"Resend email {{remaining_time}}","662578726":"Available","683273691":"Rate (1 {{ account_currency }})","723172934":"Looking to buy or sell USD? You can post your own ad for others to respond.","728383001":"I’ve received more than the agreed amount.","733311523":"P2P transactions are locked. This feature is not available for payment agents.","767789372":"Wait for payment","782834680":"Time left","783454335":"Yes, remove","830703311":"My profile","834075131":"Blocked advertisers","838024160":"Bank details","842911528":"Don’t show this message again.","846659545":"Your ad is not listed on <0>Buy/Sell because the amount exceeds your daily limit of {{limit}} {{currency}}.\n <1 /><1 />You can still see your ad on <0>My ads. If you’d like to increase your daily limit, please contact us via <2>live chat.","847028402":"Check your email","858027714":"Seen {{ duration }} minutes ago","873437248":"Instructions (optional)","876086855":"Complete the financial assessment form","881351325":"Would you recommend this seller?","887667868":"Order","892431976":"If you cancel your order {{cancellation_limit}} times in {{cancellation_period}} hours, you will be blocked from using Deriv P2P for {{block_duration}} hours.
({{number_of_cancels_remaining}} cancellations remaining)","949859957":"Submit","954233511":"Sold","957529514":"To place an order, add one of the advertiser’s preferred payment methods:","957807235":"Blocking wasn't possible as {{name}} is not using Deriv P2P anymore.","988380202":"Your instructions","1001160515":"Sell","1002264993":"Seller's real name","1020552673":"You're creating an ad to buy <0>{{ target_amount }} {{ target_currency }}...","1030390916":"You already have an ad with this range","1035893169":"Delete","1052094244":"Max order","1056821534":"Are you sure?","1057127276":"{{- avg_release_time_in_minutes}} min","1065551550":"Set floating rate","1080990424":"Confirm","1089110190":"You accidentally gave us another email address (usually a work or a personal one instead of the one you meant).","1091533736":"Don't risk your funds with cash transactions. Use bank transfers or e-wallets instead.","1106073960":"You've created an ad","1106485202":"Available Deriv P2P balance","1109217274":"Success!","1119887091":"Verification","1121630246":"Block","1137964885":"Can only contain letters, numbers, and special characters .- _ @.","1151608942":"Total amount","1157877436":"{{field_name}} should not exceed Amount","1161621759":"Choose your nickname","1162965175":"Buyer","1163072833":"<0>ID verified","1191941618":"Enter a value that's within -{{limit}}% to +{{limit}}%","1192337383":"Seen {{ duration }} hour ago","1202500203":"Pay now","1228352589":"Not rated yet","1229976478":"You will be able to see {{ advertiser_name }}'s ads. They'll be able to place orders on your ads, too.","1236083813":"Your payment details","1258285343":"Oops, something went wrong","1265751551":"Deriv P2P Balance","1286797620":"Active","1287051975":"Nickname is too long","1300767074":"{{name}} is no longer on Deriv P2P","1303016265":"Yes","1313218101":"Rate this transaction","1314266187":"Joined today","1320670806":"Leave page","1326475003":"Activate","1328352136":"Sell {{ account_currency }}","1330528524":"Seen {{ duration }} month ago","1337027601":"You sold {{offered_amount}} {{offered_currency}}","1347322213":"How would you rate this transaction?","1347724133":"I have paid {{amount}} {{currency}}.","1366244749":"Limits","1370999551":"Floating rate","1371193412":"Cancel","1381949324":"<0>Address verified","1398938904":"We can't deliver the email to this address (usually because of firewalls or filtering).","1422356389":"No results for \"{{text}}\".","1430413419":"Maximum is {{value}} {{currency}}","1438103743":"Floating rates are enabled for {{local_currency}}. Ads with fixed rates will be deactivated. Switch to floating rates by {{end_date}}.","1448855725":"Add payment methods","1452260922":"Too many failed attempts","1467483693":"Past orders","1474532322":"Sort by","1480915523":"Skip","1497156292":"No ads for this currency 😞","1505293001":"Trade partners","1568512719":"Your daily limits have been increased to {{daily_buy_limit}} {{currency}} (buy) and {{daily_sell_limit}} {{currency}} (sell).","1583335572":"If the ad doesn't receive an order for {{adverts_archive_period}} days, it will be deactivated.","1587250288":"Ad ID {{advert_id}} ","1607051458":"Search by nickname","1615530713":"Something's not right","1620858613":"You're editing an ad to sell <0>{{ target_amount }} {{ target_currency }} for <0>{{ local_amount }} {{ local_currency }} <1>({{ price_rate }} {{local_currency}}/{{ target_currency }})","1623916605":"I wasn’t able to make full payment.","1654365787":"Unknown","1660278694":"The advertiser changed the rate before you confirmed the order.","1671725772":"If you choose to cancel, the edited details will be lost.","1675716253":"Min limit","1678804253":"Buy {{ currency }}","1685888862":"An internal error occurred","1691540875":"Edit payment method","1703154819":"You're editing an ad to sell <0>{{ target_amount }} {{ target_currency }}...","1721422292":"Show my real name","1734661732":"Your DP2P balance is {{ dp2p_balance }}","1738504192":"E-wallet","1747523625":"Go back","1752096323":"{{field_name}} should not be below Min limit","1767817594":"Buy completion <0>30d","1784151356":"at","1791767028":"Set a fixed rate for your ad.","1794470010":"I’ve made full payment, but the seller hasn’t released the funds.","1794474847":"I've received payment","1798116519":"Available amount","1809099720":"Expand all","1810217569":"Please refresh this page to continue.","1842172737":"You've received {{offered_amount}} {{offered_currency}}","1848044659":"You have no ads.","1859308030":"Give feedback","1874956952":"Hit the button below to add payment methods.","1886623509":"{{ad_type}} {{ account_currency }}","1902229457":"Unable to block advertiser","1908023954":"Sorry, an error occurred while processing your request.","1923443894":"Inactive","1928240840":"Sell {{ currency }}","1929119945":"There are no ads yet","1976156928":"You'll send","1992961867":"Rate (1 {{currency}})","1994023526":"The email address you entered had a mistake or typo (happens to the best of us).","2020104747":"Filter","2029375371":"Payment instructions","2032274854":"Recommended by {{recommended_count}} traders","2039361923":"You're creating an ad to sell...","2040110829":"Increase my limits","2060873863":"Your order {{order_id}} is complete","2063890788":"Cancelled","2091671594":"Status","2096014107":"Apply","2104905634":"No one has recommended this trader yet","2121837513":"Minimum is {{value}} {{currency}}","2142425493":"Ad ID","2142752968":"Please ensure you've received {{amount}} {{local_currency}} in your account and hit Confirm to complete the transaction.","2145292295":"Rate","-1837059346":"Buy / Sell","-1845037007":"Advertiser's page","-494667560":"Orders","-679691613":"My ads","-526636259":"Error 404","-1540251249":"Buy {{ account_currency }}","-1267880283":"{{field_name}} is required","-2019083683":"{{field_name}} can only include letters, numbers, spaces, and any of these symbols: -+.,'#@():;","-222920564":"{{field_name}} has exceeded maximum length","-2093768906":"{{name}} has released your funds.
Would you like to give your feedback?","-857786650":"Check your verification status.","-612892886":"We’ll need you to upload your documents to verify your identity.","-2090325029":"Identity verification is complete.","-1101273282":"Nickname is required","-919203928":"Nickname is too short","-1907100457":"Cannot start, end with, or repeat special characters.","-270502067":"Cannot repeat a character more than 4 times.","-499872405":"You have open orders for this ad. Complete all open orders before deleting this ad.","-2125702445":"Instructions","-1274358564":"Max limit","-1995606668":"Amount","-1965472924":"Fixed rate","-1081775102":"{{field_name}} should not be below Max limit","-885044836":"{{field_name}} should not exceed Max limit","-1921077416":"All ({{list_value}})","-608125128":"Blocked ({{list_value}})","-1764050750":"Payment details","-2021135479":"This field is required.","-2005205076":"{{field_name}} has exceeded maximum length of 200 characters.","-480724783":"You already have an ad with this rate","-1207312691":"Completed","-688728873":"Expired","-1951641340":"Under dispute","-1738697484":"Confirm payment","-1611857550":"Waiting for the seller to confirm","-1452684930":"Buyer's real name","-1597110099":"Receive","-892663026":"Your contact details","-1875343569":"Seller's payment details","-92830427":"Seller's instructions","-1940034707":"Buyer's instructions","-137444201":"Buy","-1306639327":"Payment methods","-904197848":"Limits {{min_order_amount_limit_display}}-{{max_order_amount_limit_display}} {{currency}}","-464361439":"{{- avg_buy_time_in_minutes}} min","-2109576323":"Sell completion <0>30d","-165392069":"Avg. release time <0>30d","-1154208372":"Trade volume <0>30d","-1887970998":"Unblocking wasn't possible as {{name}} is not using Deriv P2P anymore.","-2017825013":"Got it","-1070228546":"Joined {{days_since_joined}}d","-2015102262":"({{number_of_ratings}} rating)","-1412298133":"({{number_of_ratings}} ratings)","-260332243":"{{user_blocked_count}} person has blocked you","-117094654":"{{user_blocked_count}} people have blocked you","-329713179":"Ok","-1689905285":"Unblock","-992568889":"No one to show here","-1298666786":"My counterparties","-1148912768":"If the market rate changes from the rate shown here, we won't be able to process your order.","-55126326":"Seller","-835196958":"Receive payment to","-1218007718":"You may choose up to 3.","-1933432699":"Enter {{transaction_type}} amount","-2021730616":"{{ad_type}}","-490637584":"Limit: {{min}}–{{max}} {{currency}}","-1974067943":"Your bank details","-1285759343":"Search","-1657433201":"There are no matching ads.","-1862812590":"Limits {{ min_order }}–{{ max_order }} {{ currency }}","-375836822":"Buy {{account_currency}}","-1035421133":"Sell {{account_currency}}","-1503997652":"No ads for this currency.","-1048001140":"No results for \"{{value}}\".","-73663931":"Create ad","-141315849":"No ads for this currency at the moment 😞","-471384801":"Sorry, we're unable to increase your limits right now. Please try again in a few minutes.","-231863107":"No","-150224710":"Yes, continue","-1638172550":"To enable this feature you must complete the following:","-559300364":"Your Deriv P2P cashier is blocked","-740038242":"Your rate is","-205277874":"Your ad is not listed on Buy/Sell because its minimum order is higher than your Deriv P2P available balance ({{balance}} {{currency}}).","-971817673":"Your ad isn't visible to others","-1735126907":"This could be because your account balance is insufficient, your ad amount exceeds your daily limit, or both. You can still see your ad on <0>My ads.","-674715853":"Your ad exceeds the daily limit","-1530773708":"Block {{advertiser_name}}?","-2035037071":"Your Deriv P2P balance isn't enough. Please increase your balance before trying again.","-412680608":"Add payment method","-293182503":"Cancel adding this payment method?","-1850127397":"If you choose to cancel, the details you’ve entered will be lost.","-1601971804":"Cancel your edits?","-1571737200":"Don't cancel","-1072444041":"Update ad","-1422779483":"That payment method cannot be deleted","-1088454544":"Get new link","-2124584325":"We've verified your order","-848068683":"Hit the link in the email we sent you to authorise this transaction.","-1238182882":"The link will expire in 10 minutes.","-142727028":"The email is in your spam folder (sometimes things get lost there).","-227512949":"Check your spelling or use a different term.","-1554938377":"Search payment method","-75934135":"Matching ads","-1856204727":"Reset","-1728351486":"Invalid verification link","-433946201":"Leave page?","-818345434":"Are you sure you want to leave this page? Changes made will not be saved.","-392043307":"Do you want to delete this ad?","-854930519":"You will NOT be able to restore it.","-1600783504":"Set a floating rate for your ad.","-2008992756":"Do you want to cancel this order?","-1618084450":"If you cancel this order, you'll be blocked from using Deriv P2P for {{block_duration}} hours.","-2026176944":"Please do not cancel if you have already made payment.","-1989544601":"Cancel this order","-492996224":"Do not cancel","-1447732068":"Payment confirmation","-1485778481":"Have you received payment?","-403938778":"Please confirm only after checking your bank or e-wallet account to make sure you have received payment.","-1875011752":"Yes, I've paid","-1146269362":"I've received {{amount}} {{currency}}","-563116612":"I haven't paid yet","-984140537":"Add","-1220275347":"You may choose up to 3 payment methods for this ad.","-1340125291":"Done","-1889014820":"<0>Don’t see your payment method? <1>Add new.","-1406830100":"Payment method","-1561775203":"Buy {{currency}}","-1527285935":"Sell {{currency}}","-592818187":"Your Deriv P2P balance is {{ dp2p_balance }}","-1654157453":"Fixed rate (1 {{currency}})","-379708059":"Min order","-1459289144":"This information will be visible to everyone.","-207756259":"You may tap and choose up to 3.","-1282343703":"You're creating an ad to buy <0>{{ target_amount }} {{ target_currency }} for <0>{{ local_amount }} {{ local_currency }} <1>({{ price_rate }} {{local_currency}}/{{ target_currency }})","-2139632895":"You're creating an ad to sell <0>{{ target_amount }} {{ target_currency }} for <0>{{ local_amount }} {{ local_currency }} <1>({{ price_rate }} {{local_currency}}/{{ target_currency }})","-40669120":"You're creating an ad to sell <0>{{ target_amount }} {{ target_currency }}...","-514789442":"You're creating an ad to buy...","-1179827369":"Create new ad","-230677679":"{{text}}","-1914431773":"You're editing an ad to buy <0>{{ target_amount }} {{ target_currency }} for <0>{{ local_amount }} {{ local_currency }} <1>({{ price_rate }} {{local_currency}}/{{ target_currency }})","-107996509":"You're editing an ad to buy <0>{{ target_amount }} {{ target_currency }}...","-863580260":"You're editing an ad to buy...","-1396464057":"You're editing an ad to sell...","-372210670":"Rate (1 {{account_currency}})","-1400835517":"{{ad_type}} {{ id }}","-1318334333":"Deactivate","-1667041441":"Rate (1 {{ offered_currency }})","-1886565882":"Your ads with floating rates have been deactivated. Set fixed rates to reactivate them.","-792015701":"Deriv P2P cashier is unavailable in your country.","-1241719539":"When you block someone, you won't see their ads, and they can't see yours. Your ads will be hidden from their search results, too.","-1007339977":"There are no matching name.","-179005984":"Save","-2059312414":"Ad details","-1769584466":"Stats","-808161760":"Deriv P2P balance = deposits that can’t be reversed","-684271315":"OK","-2090878601":"Daily limit","-474123616":"Want to increase your daily limits to <0>{{max_daily_buy}} {{currency}} (buy) and <1>{{max_daily_sell}} {{currency}} (sell)?","-130547447":"Trade volume <0>30d | <1>lifetime","-1792280476":"Choose your payment method","-383030149":"You haven’t added any payment methods yet","-1269362917":"Add new","-146021156":"Delete {{payment_method_name}}?","-1846700504":"Are you sure you want to remove this payment method?","-532709160":"Your nickname","-1117584385":"Seen more than 6 months ago","-1766199849":"Seen {{ duration }} months ago","-591593016":"Seen {{ duration }} day ago","-1586918919":"Seen {{ duration }} hours ago","-664781013":"Seen {{ duration }} minute ago","-1717650468":"Online","-510341549":"I’ve received less than the agreed amount.","-650030360":"I’ve paid more than the agreed amount.","-1192446042":"If your complaint isn't listed here, please contact our Customer Support team.","-573132778":"Complaint","-792338456":"What's your complaint?","-418870584":"Cancel order","-1392383387":"I've paid","-727273667":"Complain","-2016990049":"Sell {{offered_currency}} order","-811190405":"Time","-961632398":"Collapse all","-415476028":"Not rated","-26434257":"You have until {{remaining_review_time}} GMT to rate this transaction.","-768709492":"Your transaction experience","-652933704":"Recommended","-84139378":"Not Recommended","-1983512566":"This conversation is closed.","-1797318839":"In case of a dispute, we will only consider the communication through Deriv P2P chat channel.","-283017497":"Retry","-979459594":"Buy/Sell","-2052184983":"Order ID","-2096350108":"Counterparty","-750202930":"Active orders","-1626659964":"I've received {{amount}} {{currency}}.","-2139303636":"You may have followed a broken link, or the page has moved to a new address.","-1448368765":"Error code: {{error_code}} page not found","-1660552437":"Return to P2P","-237014436":"Recommended by {{recommended_count}} trader","-849068301":"Loading...","-2061807537":"Something’s not right","-1354983065":"Refresh","-2054589794":"You've been temporarily barred from using our services due to multiple cancellation attempts. Try again after {{date_time}} GMT.","-1079963355":"trades","-930400128":"To use Deriv P2P, you need to choose a display name (a nickname) and verify your identity."} \ No newline at end of file +{"6794664":"Ads that match your Deriv P2P balance and limit.","19789721":"Nobody has blocked you. Yay!","24711354":"Total orders <0>30d | <1>lifetime","47573834":"Fixed rate (1 {{account_currency}})","50672601":"Bought","51881712":"You already have an ad with the same exchange rate for this currency pair and order type.

Please set a different rate for your ad.","55916349":"All","68867477":"Order ID {{ id }}","81450871":"We couldn’t find that page","121738739":"Send","122280248":"Avg release time <0>30d","134205943":"Your ads with fixed rates have been deactivated. Set floating rates to reactivate them.","140800401":"Float","145959105":"Choose a nickname","150156106":"Save changes","159757877":"You won't see {{advertiser_name}}'s ads anymore and they won't be able to place orders on your ads.","170072126":"Seen {{ duration }} days ago","173939998":"Avg. pay time <0>30d","197477687":"Edit {{ad_type}} ad","203271702":"Try again","231473252":"Preferred currency","233677840":"of the market rate","246815378":"Once set, your nickname cannot be changed.","276261353":"Avg pay time <0>30d","277542386":"Please use <0>live chat to contact our Customer Support team for help.","316725580":"You can no longer rate this transaction.","323002325":"Post ad","324970564":"Seller's contact details","338910048":"You will appear to other users as","358133589":"Unblock {{advertiser_name}}?","364681129":"Contact details","367579676":"Blocked","392469164":"You have blocked {{advertiser_name}}.","416167062":"You'll receive","424668491":"expired","439264204":"Please set a different minimum and/or maximum order limit.

The range of your ad should not overlap with any of your active ads.","452752527":"Rate (1 {{ currency }})","459886707":"E-wallets","460477293":"Enter message","464044457":"Buyer's nickname","473688701":"Enter a valid amount","476023405":"Didn't receive the email?","488150742":"Resend email","498500965":"Seller's nickname","500514593":"Hide my ads","501523417":"You have no orders.","517202770":"Set fixed rate","523301614":"Release {{amount}} {{currency}}","525380157":"Buy {{offered_currency}} order","531912261":"Bank name, account number, beneficiary name","554135844":"Edit","555447610":"You won't be able to change your buy and sell limits again after this. Do you want to continue?","560402954":"User rating","565060416":"Exchange rate","580715136":"Please register with us!","587882987":"Advertisers","611376642":"Clear","612069973":"Would you recommend this buyer?","628581263":"The {{local_currency}} market rate has changed.","639382772":"Please upload supported file type.","649549724":"I’ve not received any payment.","654193846":"The verification link appears to be invalid. Hit the button below to request for a new one","655733440":"Others","661808069":"Resend email {{remaining_time}}","662578726":"Available","683273691":"Rate (1 {{ account_currency }})","723172934":"Looking to buy or sell USD? You can post your own ad for others to respond.","728383001":"I’ve received more than the agreed amount.","733311523":"P2P transactions are locked. This feature is not available for payment agents.","767789372":"Wait for payment","782834680":"Time left","830703311":"My profile","834075131":"Blocked advertisers","838024160":"Bank details","842911528":"Don’t show this message again.","846659545":"Your ad is not listed on <0>Buy/Sell because the amount exceeds your daily limit of {{limit}} {{currency}}.\n <1 /><1 />You can still see your ad on <0>My ads. If you’d like to increase your daily limit, please contact us via <2>live chat.","847028402":"Check your email","858027714":"Seen {{ duration }} minutes ago","873437248":"Instructions (optional)","876086855":"Complete the financial assessment form","881351325":"Would you recommend this seller?","887667868":"Order","892431976":"If you cancel your order {{cancellation_limit}} times in {{cancellation_period}} hours, you will be blocked from using Deriv P2P for {{block_duration}} hours.
({{number_of_cancels_remaining}} cancellations remaining)","949859957":"Submit","954233511":"Sold","957529514":"To place an order, add one of the advertiser’s preferred payment methods:","957807235":"Blocking wasn't possible as {{name}} is not using Deriv P2P anymore.","988380202":"Your instructions","1001160515":"Sell","1002264993":"Seller's real name","1020552673":"You're creating an ad to buy <0>{{ target_amount }} {{ target_currency }}...","1030390916":"You already have an ad with this range","1035893169":"Delete","1052094244":"Max order","1056821534":"Are you sure?","1057127276":"{{- avg_release_time_in_minutes}} min","1065551550":"Set floating rate","1080990424":"Confirm","1089110190":"You accidentally gave us another email address (usually a work or a personal one instead of the one you meant).","1091533736":"Don't risk your funds with cash transactions. Use bank transfers or e-wallets instead.","1106073960":"You've created an ad","1106485202":"Available Deriv P2P balance","1109217274":"Success!","1119887091":"Verification","1121630246":"Block","1137964885":"Can only contain letters, numbers, and special characters .- _ @.","1151608942":"Total amount","1157877436":"{{field_name}} should not exceed Amount","1161621759":"Choose your nickname","1162965175":"Buyer","1163072833":"<0>ID verified","1164771858":"I’ve received payment from 3rd party.","1191941618":"Enter a value that's within -{{limit}}% to +{{limit}}%","1192337383":"Seen {{ duration }} hour ago","1202500203":"Pay now","1228352589":"Not rated yet","1229976478":"You will be able to see {{ advertiser_name }}'s ads. They'll be able to place orders on your ads, too.","1236083813":"Your payment details","1258285343":"Oops, something went wrong","1265751551":"Deriv P2P Balance","1286797620":"Active","1287051975":"Nickname is too long","1300767074":"{{name}} is no longer on Deriv P2P","1303016265":"Yes","1313218101":"Rate this transaction","1314266187":"Joined today","1320670806":"Leave page","1326475003":"Activate","1328352136":"Sell {{ account_currency }}","1330528524":"Seen {{ duration }} month ago","1337027601":"You sold {{offered_amount}} {{offered_currency}}","1347322213":"How would you rate this transaction?","1347724133":"I have paid {{amount}} {{currency}}.","1366244749":"Limits","1370999551":"Floating rate","1371193412":"Cancel","1381949324":"<0>Address verified","1398938904":"We can't deliver the email to this address (usually because of firewalls or filtering).","1422356389":"No results for \"{{text}}\".","1430413419":"Maximum is {{value}} {{currency}}","1438103743":"Floating rates are enabled for {{local_currency}}. Ads with fixed rates will be deactivated. Switch to floating rates by {{end_date}}.","1448855725":"Add payment methods","1452260922":"Too many failed attempts","1467483693":"Past orders","1474532322":"Sort by","1480915523":"Skip","1497156292":"No ads for this currency 😞","1505293001":"Trade partners","1568512719":"Your daily limits have been increased to {{daily_buy_limit}} {{currency}} (buy) and {{daily_sell_limit}} {{currency}} (sell).","1583335572":"If the ad doesn't receive an order for {{adverts_archive_period}} days, it will be deactivated.","1587250288":"Ad ID {{advert_id}} ","1607051458":"Search by nickname","1612595358":"Cannot upload a file over 2MB","1615530713":"Something's not right","1620858613":"You're editing an ad to sell <0>{{ target_amount }} {{ target_currency }} for <0>{{ local_amount }} {{ local_currency }} <1>({{ price_rate }} {{local_currency}}/{{ target_currency }})","1623916605":"I wasn’t able to make full payment.","1654365787":"Unknown","1660278694":"The advertiser changed the rate before you confirmed the order.","1671725772":"If you choose to cancel, the edited details will be lost.","1675716253":"Min limit","1678804253":"Buy {{ currency }}","1685888862":"An internal error occurred","1691540875":"Edit payment method","1703154819":"You're editing an ad to sell <0>{{ target_amount }} {{ target_currency }}...","1721422292":"Show my real name","1734661732":"Your DP2P balance is {{ dp2p_balance }}","1747523625":"Go back","1752096323":"{{field_name}} should not be below Min limit","1767817594":"Buy completion <0>30d","1784151356":"at","1791767028":"Set a fixed rate for your ad.","1794470010":"I’ve made full payment, but the seller hasn’t released the funds.","1794474847":"I've received payment","1798116519":"Available amount","1809099720":"Expand all","1810217569":"Please refresh this page to continue.","1842172737":"You've received {{offered_amount}} {{offered_currency}}","1848044659":"You have no ads.","1859308030":"Give feedback","1874956952":"Hit the button below to add payment methods.","1886623509":"{{ad_type}} {{ account_currency }}","1902229457":"Unable to block advertiser","1908023954":"Sorry, an error occurred while processing your request.","1923443894":"Inactive","1928240840":"Sell {{ currency }}","1929119945":"There are no ads yet","1976156928":"You'll send","1992961867":"Rate (1 {{currency}})","1994023526":"The email address you entered had a mistake or typo (happens to the best of us).","2020104747":"Filter","2029375371":"Payment instructions","2032274854":"Recommended by {{recommended_count}} traders","2039361923":"You're creating an ad to sell...","2040110829":"Increase my limits","2060873863":"Your order {{order_id}} is complete","2063890788":"Cancelled","2091671594":"Status","2096014107":"Apply","2104905634":"No one has recommended this trader yet","2121837513":"Minimum is {{value}} {{currency}}","2142425493":"Ad ID","2142752968":"Please ensure you've received {{amount}} {{local_currency}} in your account and hit Confirm to complete the transaction.","2145292295":"Rate","-1837059346":"Buy / Sell","-1845037007":"Advertiser's page","-494667560":"Orders","-679691613":"My ads","-526636259":"Error 404","-1540251249":"Buy {{ account_currency }}","-1267880283":"{{field_name}} is required","-2019083683":"{{field_name}} can only include letters, numbers, spaces, and any of these symbols: -+.,'#@():;","-222920564":"{{field_name}} has exceeded maximum length","-2093768906":"{{name}} has released your funds.
Would you like to give your feedback?","-857786650":"Check your verification status.","-612892886":"We’ll need you to upload your documents to verify your identity.","-2090325029":"Identity verification is complete.","-1101273282":"Nickname is required","-919203928":"Nickname is too short","-1907100457":"Cannot start, end with, or repeat special characters.","-270502067":"Cannot repeat a character more than 4 times.","-499872405":"You have open orders for this ad. Complete all open orders before deleting this ad.","-2125702445":"Instructions","-1274358564":"Max limit","-1995606668":"Amount","-1965472924":"Fixed rate","-1081775102":"{{field_name}} should not be below Max limit","-885044836":"{{field_name}} should not exceed Max limit","-1921077416":"All ({{list_value}})","-608125128":"Blocked ({{list_value}})","-1764050750":"Payment details","-2021135479":"This field is required.","-2005205076":"{{field_name}} has exceeded maximum length of 200 characters.","-480724783":"You already have an ad with this rate","-1948369500":"File uploaded is not supported","-1207312691":"Completed","-688728873":"Expired","-1951641340":"Under dispute","-1738697484":"Confirm payment","-1611857550":"Waiting for the seller to confirm","-1452684930":"Buyer's real name","-1597110099":"Receive","-892663026":"Your contact details","-1875343569":"Seller's payment details","-92830427":"Seller's instructions","-1940034707":"Buyer's instructions","-137444201":"Buy","-1306639327":"Payment methods","-904197848":"Limits {{min_order_amount_limit_display}}-{{max_order_amount_limit_display}} {{currency}}","-464361439":"{{- avg_buy_time_in_minutes}} min","-2109576323":"Sell completion <0>30d","-165392069":"Avg. release time <0>30d","-1154208372":"Trade volume <0>30d","-1887970998":"Unblocking wasn't possible as {{name}} is not using Deriv P2P anymore.","-2017825013":"Got it","-1070228546":"Joined {{days_since_joined}}d","-2015102262":"({{number_of_ratings}} rating)","-1412298133":"({{number_of_ratings}} ratings)","-260332243":"{{user_blocked_count}} person has blocked you","-117094654":"{{user_blocked_count}} people have blocked you","-329713179":"Ok","-1689905285":"Unblock","-992568889":"No one to show here","-1298666786":"My counterparties","-1148912768":"If the market rate changes from the rate shown here, we won't be able to process your order.","-55126326":"Seller","-835196958":"Receive payment to","-1218007718":"You may choose up to 3.","-1933432699":"Enter {{transaction_type}} amount","-2021730616":"{{ad_type}}","-490637584":"Limit: {{min}}–{{max}} {{currency}}","-1974067943":"Your bank details","-1285759343":"Search","-1657433201":"There are no matching ads.","-1862812590":"Limits {{ min_order }}–{{ max_order }} {{ currency }}","-375836822":"Buy {{account_currency}}","-1035421133":"Sell {{account_currency}}","-1503997652":"No ads for this currency.","-1048001140":"No results for \"{{value}}\".","-73663931":"Create ad","-141315849":"No ads for this currency at the moment 😞","-471384801":"Sorry, we're unable to increase your limits right now. Please try again in a few minutes.","-231863107":"No","-150224710":"Yes, continue","-1638172550":"To enable this feature you must complete the following:","-559300364":"Your Deriv P2P cashier is blocked","-740038242":"Your rate is","-205277874":"Your ad is not listed on Buy/Sell because its minimum order is higher than your Deriv P2P available balance ({{balance}} {{currency}}).","-971817673":"Your ad isn't visible to others","-1735126907":"This could be because your account balance is insufficient, your ad amount exceeds your daily limit, or both. You can still see your ad on <0>My ads.","-674715853":"Your ad exceeds the daily limit","-1530773708":"Block {{advertiser_name}}?","-2035037071":"Your Deriv P2P balance isn't enough. Please increase your balance before trying again.","-412680608":"Add payment method","-293182503":"Cancel adding this payment method?","-1850127397":"If you choose to cancel, the details you’ve entered will be lost.","-1601971804":"Cancel your edits?","-1571737200":"Don't cancel","-1072444041":"Update ad","-1088454544":"Get new link","-2124584325":"We've verified your order","-848068683":"Hit the link in the email we sent you to authorise this transaction.","-1238182882":"The link will expire in 10 minutes.","-142727028":"The email is in your spam folder (sometimes things get lost there).","-227512949":"Check your spelling or use a different term.","-1554938377":"Search payment method","-75934135":"Matching ads","-1856204727":"Reset","-1728351486":"Invalid verification link","-433946201":"Leave page?","-818345434":"Are you sure you want to leave this page? Changes made will not be saved.","-392043307":"Do you want to delete this ad?","-854930519":"You will NOT be able to restore it.","-1600783504":"Set a floating rate for your ad.","-2008992756":"Do you want to cancel this order?","-1618084450":"If you cancel this order, you'll be blocked from using Deriv P2P for {{block_duration}} hours.","-2026176944":"Please do not cancel if you have already made payment.","-1989544601":"Cancel this order","-492996224":"Do not cancel","-1447732068":"Payment confirmation","-1951344681":"Please make sure that you've paid {{amount}} {{currency}} to {{other_user_name}}, and upload the receipt as proof of your payment","-919988505":"We accept JPG, PDF, or PNG (up to 2MB).","-670364940":"Upload receipt here","-937707753":"Go Back","-984140537":"Add","-1220275347":"You may choose up to 3 payment methods for this ad.","-1340125291":"Done","-1889014820":"<0>Don’t see your payment method? <1>Add new.","-1406830100":"Payment method","-1561775203":"Buy {{currency}}","-1527285935":"Sell {{currency}}","-592818187":"Your Deriv P2P balance is {{ dp2p_balance }}","-1654157453":"Fixed rate (1 {{currency}})","-379708059":"Min order","-1459289144":"This information will be visible to everyone.","-207756259":"You may tap and choose up to 3.","-1282343703":"You're creating an ad to buy <0>{{ target_amount }} {{ target_currency }} for <0>{{ local_amount }} {{ local_currency }} <1>({{ price_rate }} {{local_currency}}/{{ target_currency }})","-2139632895":"You're creating an ad to sell <0>{{ target_amount }} {{ target_currency }} for <0>{{ local_amount }} {{ local_currency }} <1>({{ price_rate }} {{local_currency}}/{{ target_currency }})","-40669120":"You're creating an ad to sell <0>{{ target_amount }} {{ target_currency }}...","-514789442":"You're creating an ad to buy...","-1179827369":"Create new ad","-230677679":"{{text}}","-1914431773":"You're editing an ad to buy <0>{{ target_amount }} {{ target_currency }} for <0>{{ local_amount }} {{ local_currency }} <1>({{ price_rate }} {{local_currency}}/{{ target_currency }})","-107996509":"You're editing an ad to buy <0>{{ target_amount }} {{ target_currency }}...","-863580260":"You're editing an ad to buy...","-1396464057":"You're editing an ad to sell...","-372210670":"Rate (1 {{account_currency}})","-1400835517":"{{ad_type}} {{ id }}","-1318334333":"Deactivate","-1667041441":"Rate (1 {{ offered_currency }})","-1886565882":"Your ads with floating rates have been deactivated. Set fixed rates to reactivate them.","-792015701":"Deriv P2P cashier is unavailable in your country.","-1241719539":"When you block someone, you won't see their ads, and they can't see yours. Your ads will be hidden from their search results, too.","-1007339977":"There are no matching name.","-179005984":"Save","-2059312414":"Ad details","-1769584466":"Stats","-808161760":"Deriv P2P balance = deposits that can’t be reversed","-684271315":"OK","-2090878601":"Daily limit","-474123616":"Want to increase your daily limits to <0>{{max_daily_buy}} {{currency}} (buy) and <1>{{max_daily_sell}} {{currency}} (sell)?","-130547447":"Trade volume <0>30d | <1>lifetime","-1792280476":"Choose your payment method","-383030149":"You haven’t added any payment methods yet","-1156559889":"Bank Transfers","-1269362917":"Add new","-532709160":"Your nickname","-1117584385":"Seen more than 6 months ago","-1766199849":"Seen {{ duration }} months ago","-591593016":"Seen {{ duration }} day ago","-1586918919":"Seen {{ duration }} hours ago","-664781013":"Seen {{ duration }} minute ago","-1717650468":"Online","-510341549":"I’ve received less than the agreed amount.","-650030360":"I’ve paid more than the agreed amount.","-1192446042":"If your complaint isn't listed here, please contact our Customer Support team.","-573132778":"Complaint","-792338456":"What's your complaint?","-418870584":"Cancel order","-1392383387":"I've paid","-727273667":"Complain","-2016990049":"Sell {{offered_currency}} order","-811190405":"Time","-961632398":"Collapse all","-415476028":"Not rated","-26434257":"You have until {{remaining_review_time}} GMT to rate this transaction.","-768709492":"Your transaction experience","-652933704":"Recommended","-84139378":"Not Recommended","-1983512566":"This conversation is closed.","-1797318839":"In case of a dispute, we will only consider the communication through Deriv P2P chat channel.","-283017497":"Retry","-979459594":"Buy/Sell","-2052184983":"Order ID","-2096350108":"Counterparty","-750202930":"Active orders","-1626659964":"I've received {{amount}} {{currency}}.","-2139303636":"You may have followed a broken link, or the page has moved to a new address.","-1448368765":"Error code: {{error_code}} page not found","-1660552437":"Return to P2P","-237014436":"Recommended by {{recommended_count}} trader","-849068301":"Loading...","-2061807537":"Something’s not right","-1354983065":"Refresh","-2054589794":"You've been temporarily barred from using our services due to multiple cancellation attempts. Try again after {{date_time}} GMT.","-1079963355":"trades","-930400128":"To use Deriv P2P, you need to choose a display name (a nickname) and verify your identity."} \ No newline at end of file diff --git a/packages/p2p/package.json b/packages/p2p/package.json index f57144370ab5..09d6fc19ded5 100644 --- a/packages/p2p/package.json +++ b/packages/p2p/package.json @@ -34,6 +34,7 @@ "@deriv/hooks": "^1.0.0", "@deriv/shared": "^1.0.0", "@deriv/stores": "^1.0.0", + "@deriv/api": "^1.0.0", "@deriv/translations": "^1.0.0", "@deriv/api-types": "^1.0.118", "@testing-library/react": "^12.0.0", diff --git a/packages/p2p/src/components/advertiser-page/advertiser-page-row.jsx b/packages/p2p/src/components/advertiser-page/advertiser-page-row.jsx index 2924093f038c..f67a37d78098 100644 --- a/packages/p2p/src/components/advertiser-page/advertiser-page-row.jsx +++ b/packages/p2p/src/components/advertiser-page/advertiser-page-row.jsx @@ -1,8 +1,9 @@ -import PropTypes from 'prop-types'; import React from 'react'; +import PropTypes from 'prop-types'; import { Button, Table, Text } from '@deriv/components'; import { isMobile } from '@deriv/shared'; import { observer, useStore } from '@deriv/stores'; +import { useExchangeRate } from '@deriv/hooks'; import { useStores } from 'Stores'; import { buy_sell } from 'Constants/buy-sell'; import { localize, Localize } from 'Components/i18next'; @@ -11,7 +12,8 @@ import { useModalManagerContext } from 'Components/modal-manager/modal-manager-c import './advertiser-page.scss'; const AdvertiserPageRow = ({ row: advert }) => { - const { advertiser_page_store, buy_sell_store, floating_rate_store, general_store } = useStores(); + const { advertiser_page_store, buy_sell_store, general_store } = useStores(); + const { getRate } = useExchangeRate(); const { client: { currency }, } = useStore(); @@ -35,7 +37,7 @@ const AdvertiserPageRow = ({ row: advert }) => { rate_type, rate, local_currency, - exchange_rate: floating_rate_store.exchange_rate, + exchange_rate: getRate(local_currency), market_rate: effective_rate, }); diff --git a/packages/p2p/src/components/buy-sell/buy-sell-form.jsx b/packages/p2p/src/components/buy-sell/buy-sell-form.jsx index 63c0602c9b59..8c972da25e2d 100644 --- a/packages/p2p/src/components/buy-sell/buy-sell-form.jsx +++ b/packages/p2p/src/components/buy-sell/buy-sell-form.jsx @@ -1,8 +1,9 @@ -import classNames from 'classnames'; import React from 'react'; +import classNames from 'classnames'; import PropTypes from 'prop-types'; import { Formik, Field, Form } from 'formik'; -import { HintBox, Icon, Input, Text } from '@deriv/components'; +import { HintBox, Input, Text } from '@deriv/components'; +import { useP2PAdvertiserPaymentMethods, useExchangeRate } from '@deriv/hooks'; import { getDecimalPlaces, isDesktop, isMobile, useIsMounted } from '@deriv/shared'; import { reaction } from 'mobx'; import { observer, Observer } from 'mobx-react-lite'; @@ -15,13 +16,15 @@ import { floatingPointValidator } from 'Utils/validations'; import { countDecimalPlaces } from 'Utils/string'; import { generateEffectiveRate, setDecimalPlaces, roundOffDecimal, removeTrailingZeros } from 'Utils/format-value'; import { useModalManagerContext } from 'Components/modal-manager/modal-manager-context'; +import PaymentMethodIcon from 'Components/payment-method-icon'; const BuySellForm = props => { const isMounted = useIsMounted(); - const { advertiser_page_store, buy_sell_store, floating_rate_store, general_store, my_profile_store } = useStores(); + const { advertiser_page_store, buy_sell_store, general_store, my_profile_store } = useStores(); const [selected_methods, setSelectedMethods] = React.useState([]); buy_sell_store.setFormProps(props); const { showModal } = useModalManagerContext(); + const { data: p2p_advertiser_payment_methods } = useP2PAdvertiserPaymentMethods(); const { setPageFooterParent } = props; const { @@ -37,6 +40,11 @@ const BuySellForm = props => { rate, rate_type, } = buy_sell_store?.advert || {}; + + const { getRate } = useExchangeRate(); + const exchange_rate = getRate(local_currency); + + const [previous_rate, setPreviousRate] = React.useState(exchange_rate); const [input_amount, setInputAmount] = React.useState(min_order_amount_limit); const { advertiser_buy_limit, advertiser_sell_limit, balance } = general_store; @@ -55,7 +63,7 @@ const BuySellForm = props => { rate_type, rate, local_currency, - exchange_rate: floating_rate_store.exchange_rate, + exchange_rate, market_rate, }); @@ -82,25 +90,11 @@ const BuySellForm = props => { } advertiser_page_store.setFormErrorMessage(''); - const disposeRateChangeModal = reaction( - () => floating_rate_store.is_market_rate_changed, - is_market_rate_changed => { - if (is_market_rate_changed && rate_type === ad_type.FLOAT) { - showModal({ - key: 'RateChangeModal', - props: { - currency: buy_sell_store.local_currency, - }, - }); - } - } - ); buy_sell_store.setInitialReceiveAmount(calculated_rate); return () => { buy_sell_store.payment_method_ids = []; disposeReceiveAmountReaction(); - disposeRateChangeModal(); }; }, [] // eslint-disable-line react-hooks/exhaustive-deps @@ -112,18 +106,30 @@ const BuySellForm = props => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [input_amount, effective_rate]); + React.useEffect(() => { + if (exchange_rate !== previous_rate && rate_type === ad_type.FLOAT) { + setPreviousRate(exchange_rate); + showModal({ + key: 'RateChangeModal', + props: { + currency: buy_sell_store.local_currency, + }, + }); + } + }, [exchange_rate, previous_rate]); + const onClickPaymentMethodCard = payment_method => { if (!should_disable_field) { - if (!buy_sell_store.payment_method_ids.includes(payment_method.ID)) { + if (!buy_sell_store.payment_method_ids.includes(payment_method.id)) { if (buy_sell_store.payment_method_ids.length < 3) { - buy_sell_store.payment_method_ids.push(payment_method.ID); - setSelectedMethods([...selected_methods, payment_method.ID]); + buy_sell_store.payment_method_ids.push(payment_method.id); + setSelectedMethods([...selected_methods, payment_method.id]); } } else { buy_sell_store.payment_method_ids = buy_sell_store.payment_method_ids.filter( - payment_method_id => payment_method_id !== payment_method.ID + payment_method_id => payment_method_id !== payment_method.id ); - setSelectedMethods(selected_methods.filter(i => i !== payment_method.ID)); + setSelectedMethods(selected_methods.filter(i => i !== payment_method.id)); } } }; @@ -213,37 +219,17 @@ const BuySellForm = props => { )} {payment_method_names && - payment_method_names.map((payment_method, key) => { - const method = payment_method.replace(/\s|-/gm, ''); - - if (method === 'BankTransfer' || method === 'Other') { - return ( -
- - - {payment_method} - -
- ); - } - - return ( -
- - - {payment_method} - -
- ); - })} + payment_method_names.map((payment_method, key) => ( +
+ + + {payment_method} + +
+ ))}
@@ -296,17 +282,17 @@ const BuySellForm = props => { {payment_method_names ?.map((add_payment_method, key) => { const { - advertiser_payment_methods_list, setSelectedPaymentMethodDisplayName, setShouldShowAddPaymentMethodForm, } = my_profile_store; const matching_payment_methods = - advertiser_payment_methods_list.filter( + p2p_advertiser_payment_methods?.filter( advertiser_payment_method => advertiser_payment_method.display_name === add_payment_method ); - return matching_payment_methods.length > 0 ? ( + + return matching_payment_methods?.length > 0 ? ( matching_payment_methods.map(payment_method => ( { payment_method={payment_method} style={ selected_methods.includes( - payment_method.ID + payment_method.id ) ? style : {} diff --git a/packages/p2p/src/components/buy-sell/buy-sell-row.jsx b/packages/p2p/src/components/buy-sell/buy-sell-row.jsx index 2b13f328e706..ffb59e90c5e0 100644 --- a/packages/p2p/src/components/buy-sell/buy-sell-row.jsx +++ b/packages/p2p/src/components/buy-sell/buy-sell-row.jsx @@ -5,6 +5,7 @@ import { useHistory } from 'react-router-dom'; import { Table, Text, Button, Icon } from '@deriv/components'; import { isMobile, routes } from '@deriv/shared'; import { observer, useStore } from '@deriv/stores'; +import { useExchangeRate } from '@deriv/hooks'; import { buy_sell } from 'Constants/buy-sell'; import { Localize, localize } from 'Components/i18next'; import { OnlineStatusAvatar } from 'Components/online-status'; @@ -15,11 +16,12 @@ import { generateEffectiveRate } from 'Utils/format-value'; import './buy-sell-row.scss'; const BuySellRow = ({ row: advert }) => { - const { buy_sell_store, floating_rate_store, general_store } = useStores(); + const { buy_sell_store, general_store } = useStores(); const { client: { currency }, } = useStore(); const history = useHistory(); + const { getRate } = useExchangeRate(); if (advert.id === 'WATCH_THIS_SPACE') { // This allows for the sliding animation on the Buy/Sell toggle as it pushes @@ -62,7 +64,7 @@ const BuySellRow = ({ row: advert }) => { rate_type, rate, local_currency, - exchange_rate: floating_rate_store.exchange_rate, + exchange_rate: getRate(local_currency), market_rate: effective_rate, }); const onClickRow = () => { diff --git a/packages/p2p/src/components/buy-sell/currency-dropdown.jsx b/packages/p2p/src/components/buy-sell/currency-dropdown.jsx index a7fea5b9e3b8..b93ea09bd191 100644 --- a/packages/p2p/src/components/buy-sell/currency-dropdown.jsx +++ b/packages/p2p/src/components/buy-sell/currency-dropdown.jsx @@ -41,8 +41,8 @@ const CurrencyDropdown = () => { default_value={selected_local_currency} list={local_currencies} onSelect={value => { - onLocalCurrencySelect(value); setIsListVisible(false); + onLocalCurrencySelect(value); }} /> )} diff --git a/packages/p2p/src/components/file-uploader-component/__test__/file-uploader-component.spec.js b/packages/p2p/src/components/file-uploader-component/__test__/file-uploader-component.spec.js new file mode 100644 index 000000000000..61acb889991e --- /dev/null +++ b/packages/p2p/src/components/file-uploader-component/__test__/file-uploader-component.spec.js @@ -0,0 +1,66 @@ +import React from 'react'; +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; +import { isMobile, isDesktop } from '@deriv/shared'; +import FileUploaderComponent from '../file-uploader-component'; + +jest.mock('@deriv/shared', () => ({ + ...jest.requireActual('@deriv/shared'), + isDesktop: jest.fn(), + isMobile: jest.fn(), + compressImageFiles: jest.fn(() => Promise.resolve([{ path: 'hello.pdf' }])), + readFiles: jest.fn(), +})); + +jest.mock('@binary-com/binary-document-uploader'); + +describe('', () => { + beforeEach(() => { + isDesktop.mockReturnValue(true); + isMobile.mockReturnValue(false); + jest.clearAllMocks(); + }); + + const file = new File(['hello'], 'hello.png', { type: 'image/png' }); + const props = { + accept: 'image/pdf, image/png', + filename_limit: 26, + max_size: 2097152, + multiple: false, + onDropAccepted: jest.fn(), + onDropRejected: jest.fn(), + validation_error_message: null, + onClickClose: jest.fn(), + upload_message: 'upload here', + value: [], + }; + + it('should render FileUploaderComponent component in desktop mode', async () => { + render(); + expect(screen.getByText('upload here')).toBeInTheDocument(); + }); + + it('should upload supported file', async () => { + props.value = [file]; + render(); + const input = screen.getByTestId('dt_file_upload_input'); + fireEvent.change(input, { target: { files: [file] } }); + await waitFor(() => { + expect(input.files[0]).toBe(file); + expect(input.files).toHaveLength(1); + }); + expect(screen.getByText('hello.png')).toBeInTheDocument(); + }); + + it('should show error message when unsupported file is uploaded', async () => { + props.validation_error_message = 'error'; + render(); + + const unsupported_file = new File(['hello'], 'hello.html', { type: 'html' }); + const input = screen.getByTestId('dt_file_upload_input'); + fireEvent.change(input, { target: { files: [unsupported_file] } }); + + await waitFor(() => { + expect(screen.getByText('error')).toBeInTheDocument(); + }); + }); +}); diff --git a/packages/p2p/src/components/file-uploader-component/file-uploader-component.jsx b/packages/p2p/src/components/file-uploader-component/file-uploader-component.jsx new file mode 100644 index 000000000000..7b0db19725f4 --- /dev/null +++ b/packages/p2p/src/components/file-uploader-component/file-uploader-component.jsx @@ -0,0 +1,72 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { FileDropzone, Icon, Text } from '@deriv/components'; +import { isMobile } from '@deriv/shared'; +import { localize } from 'Components/i18next'; + +const FileUploaderComponent = ({ + accept, + hover_message, + max_size, + multiple = false, + onClickClose, + onDropAccepted, + onDropRejected, + upload_message, + validation_error_message, + value, +}) => { + const getUploadMessage = () => { + return ( + <> + + + {upload_message} + + + ); + }; + + return ( +
+ + {(value.length > 0 || !!validation_error_message) && ( + + )} +
+ ); +}; + +FileUploaderComponent.propTypes = { + accept: PropTypes.string, + hover_message: PropTypes.string, + error_messages: PropTypes.string, + max_size: PropTypes.number, + multiple: PropTypes.bool, + upload_message: PropTypes.string, + onClickClose: PropTypes.func, + onDropAccepted: PropTypes.func, + onDropRejected: PropTypes.func, + validation_error_message: PropTypes.string, + value: PropTypes.arrayOf(PropTypes.instanceOf(File)), +}; + +export default FileUploaderComponent; diff --git a/packages/p2p/src/components/file-uploader-component/file-uploader-component.scss b/packages/p2p/src/components/file-uploader-component/file-uploader-component.scss new file mode 100644 index 000000000000..fa3be2feabb5 --- /dev/null +++ b/packages/p2p/src/components/file-uploader-component/file-uploader-component.scss @@ -0,0 +1,23 @@ +.file-uploader-component { + .dc-file-dropzone { + height: 14rem; + border: 1px solid var(--general-active); + &__filename { + font-size: 1.4rem; + line-height: 2rem; + @include mobile { + font-size: 1.2rem; + line-height: 1.8rem; + } + } + } + position: relative; + &__close-icon { + position: absolute; + top: 0.8rem; + right: 0.8rem; + &:hover { + cursor: pointer; + } + } +} diff --git a/packages/p2p/src/components/file-uploader-component/index.js b/packages/p2p/src/components/file-uploader-component/index.js new file mode 100644 index 000000000000..3d3e6ed215c8 --- /dev/null +++ b/packages/p2p/src/components/file-uploader-component/index.js @@ -0,0 +1,4 @@ +import FileUploaderComponent from './file-uploader-component.jsx'; +import './file-uploader-component.scss'; + +export default FileUploaderComponent; diff --git a/packages/p2p/src/components/floating-rate/__test__/floating-rate.spec.js b/packages/p2p/src/components/floating-rate/__test__/floating-rate.spec.js index a840345989ab..dbba08886a5b 100644 --- a/packages/p2p/src/components/floating-rate/__test__/floating-rate.spec.js +++ b/packages/p2p/src/components/floating-rate/__test__/floating-rate.spec.js @@ -2,65 +2,33 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; import FloatingRate from '../floating-rate.jsx'; import { mockStore, StoreProvider } from '@deriv/stores'; +import { APIProvider } from '@deriv/api'; -jest.mock('Stores', () => ({ - ...jest.requireActual('Stores'), - useStores: jest.fn().mockReturnValue({ - general_store: { - current_focus: '', - client: { local_currency_config: { decimal_places: 2 } }, - setCurrentFocus: jest.fn(), - }, - floating_rate_store: { - market_rate: '100', - }, - }), -})); +const wrapper = ({ children }) => ( + + {children} + +); -jest.mock('@sendbird/chat', () => ({ - SendbirdChat: jest.fn().mockReturnValue({}), -})); - -jest.mock('@sendbird/chat/groupChannel', () => ({ - SendbirdChat: jest.fn().mockReturnValue({}), -})); - -jest.mock('@sendbird/chat/message', () => ({ - SendbirdChat: jest.fn().mockReturnValue({}), -})); - -describe('', () => { +describe('FloatingRate component', () => { it('should render default state of the component with hint message and increment, decrement buttons', () => { - render(, { - // TODO: remove StoreProvider Wrappers when we fix routing for p2p - wrapper: ({ children }) => {children}, - }); - + render(, { wrapper }); expect(screen.getByText('of the market rate')).toBeInTheDocument(); expect(screen.getAllByRole('button')).toHaveLength(2); }); it('should display error messages when error is passed as props', () => { - render(, { - wrapper: ({ children }) => {children}, - }); - + render(, { wrapper }); expect(screen.getByText('Floating rate error')).toBeInTheDocument(); }); it('should render market rate feed based on the floating rate value passed', () => { - render(, { - wrapper: ({ children }) => {children}, - }); - - expect(screen.getByText('Your rate is = 102.00')).toBeInTheDocument(); + render(, { wrapper }); + expect(screen.getByText('Your rate is = 1.02')).toBeInTheDocument(); }); it('should render the exchange rate in hint', () => { - render(, { - wrapper: ({ children }) => {children}, - }); - - expect(screen.getByText('1 AED = 100.00')).toBeInTheDocument(); + render(, { wrapper }); + expect(screen.getByText('1 AED = 1.00')).toBeInTheDocument(); }); }); diff --git a/packages/p2p/src/components/floating-rate/floating-rate.jsx b/packages/p2p/src/components/floating-rate/floating-rate.jsx index a9147d9e9d58..014199af302a 100644 --- a/packages/p2p/src/components/floating-rate/floating-rate.jsx +++ b/packages/p2p/src/components/floating-rate/floating-rate.jsx @@ -1,11 +1,11 @@ +import React from 'react'; import classNames from 'classnames'; import PropTypes from 'prop-types'; -import React from 'react'; import { InputField, Text } from '@deriv/components'; import { formatMoney, isMobile, mobileOSDetect } from '@deriv/shared'; import { observer, useStore } from '@deriv/stores'; +import { useExchangeRate, useP2PConfig } from '@deriv/hooks'; import { localize } from 'Components/i18next'; -import { useStores } from 'Stores'; import { setDecimalPlaces, removeTrailingZeros, percentOf, roundOffDecimal } from 'Utils/format-value'; import './floating-rate.scss'; @@ -24,10 +24,13 @@ const FloatingRate = ({ ui: { current_focus, setCurrentFocus }, } = useStore(); - const { floating_rate_store } = useStores(); + const { data: p2p_config } = useP2PConfig(); + const { getRate } = useExchangeRate(); + const override_exchange_rate = p2p_config?.override_exchange_rate; + const market_rate = override_exchange_rate ? Number(override_exchange_rate) : getRate(local_currency); const os = mobileOSDetect(); const { name, value, required } = props; - const market_feed = value ? percentOf(floating_rate_store.market_rate, value) : floating_rate_store.market_rate; + const market_feed = value ? percentOf(market_rate, value) : market_rate; const decimal_place = setDecimalPlaces(market_feed, 6); // Input mask for formatting value on blur of floating rate field @@ -95,8 +98,7 @@ const FloatingRate = ({ line_height='xs' className='floating-rate__mkt-rate--msg' > - 1 {fiat_currency} ={' '} - {removeTrailingZeros(formatMoney(local_currency, floating_rate_store.market_rate, true, 6))} + 1 {fiat_currency} = {removeTrailingZeros(formatMoney(local_currency, market_rate, true, 6))}
diff --git a/packages/p2p/src/components/modal-manager/modals/add-payment-method-error-modal.jsx b/packages/p2p/src/components/modal-manager/modals/add-payment-method-error-modal.jsx index 49ef39623ecd..4d9d120cae7c 100644 --- a/packages/p2p/src/components/modal-manager/modals/add-payment-method-error-modal.jsx +++ b/packages/p2p/src/components/modal-manager/modals/add-payment-method-error-modal.jsx @@ -6,7 +6,7 @@ import { observer } from 'mobx-react-lite'; import { localize } from 'Components/i18next'; const AddPaymentMethodErrorModal = () => { - const { my_profile_store } = useStores(); + const { general_store, my_profile_store } = useStores(); const { is_modal_open, hideModal } = useModalManagerContext(); return ( @@ -21,6 +21,9 @@ const AddPaymentMethodErrorModal = () => { text={localize('Ok')} onClick={() => { my_profile_store.setAddPaymentMethodErrorMessage(''); + my_profile_store.setSelectedPaymentMethod(''); + general_store.setSavedFormState(null); + general_store.setFormikRef(null); hideModal({ should_save_form_history: true, }); diff --git a/packages/p2p/src/components/modal-manager/modals/buy-sell-modal/buy-sell-modal.jsx b/packages/p2p/src/components/modal-manager/modals/buy-sell-modal/buy-sell-modal.jsx index bb503dc00fcf..9f81c54102fb 100644 --- a/packages/p2p/src/components/modal-manager/modals/buy-sell-modal/buy-sell-modal.jsx +++ b/packages/p2p/src/components/modal-manager/modals/buy-sell-modal/buy-sell-modal.jsx @@ -1,6 +1,6 @@ +import React from 'react'; import classNames from 'classnames'; import PropTypes from 'prop-types'; -import React from 'react'; import { Button, DesktopWrapper, @@ -109,7 +109,7 @@ const BuySellModalTitle = () => { }; const BuySellModal = () => { - const { buy_sell_store, floating_rate_store, general_store, my_profile_store, order_store } = useStores(); + const { buy_sell_store, general_store, my_profile_store, order_store } = useStores(); const submitForm = React.useRef(() => {}); const [error_message, setErrorMessage] = useSafeState(null); const [is_submit_disabled, setIsSubmitDisabled] = useSafeState(true); @@ -206,7 +206,6 @@ const BuySellModal = () => { hideModal(); buy_sell_store.fetchAdvertiserAdverts(); } - floating_rate_store.setIsMarketRateChanged(false); }; const onConfirmClick = order_info => { diff --git a/packages/p2p/src/components/modal-manager/modals/delete-payment-method-confirmation-modal/__tests__/delete-payment-method-confirmation-modal.spec.tsx b/packages/p2p/src/components/modal-manager/modals/delete-payment-method-confirmation-modal/__tests__/delete-payment-method-confirmation-modal.spec.tsx new file mode 100644 index 000000000000..d4733aec548c --- /dev/null +++ b/packages/p2p/src/components/modal-manager/modals/delete-payment-method-confirmation-modal/__tests__/delete-payment-method-confirmation-modal.spec.tsx @@ -0,0 +1,67 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import DeletePaymentMethodConfirmationModal from '../delete-payment-method-confirmation-modal'; + +const mock_modal_manager_context = { + hideModal: jest.fn(), + is_modal_open: true, + showModal: jest.fn(), +}; + +const mock_p2p_advertiser_payment_methods_hooks = { + delete: jest.fn(), + mutation: { + error: undefined, + status: 'idle', + }, +}; + +jest.mock('Components/modal-manager/modal-manager-context', () => ({ + useModalManagerContext: () => mock_modal_manager_context, +})); + +jest.mock('@deriv/hooks', () => ({ + ...jest.requireActual('@deriv/hooks'), + useP2PAdvertiserPaymentMethods: jest.fn(() => mock_p2p_advertiser_payment_methods_hooks), +})); + +describe('', () => { + let modal_root_el: HTMLElement; + beforeAll(() => { + modal_root_el = document.createElement('div'); + modal_root_el.setAttribute('id', 'modal_root'); + document.body.appendChild(modal_root_el); + }); + + afterAll(() => { + document.body.removeChild(modal_root_el); + }); + + it('should render delete payment method confirmation modal', () => { + render(); + + expect(screen.getByText('Delete Skrill?')).toBeInTheDocument(); + expect(screen.getByText('Are you sure you want to remove this payment method?')).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Yes, remove' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'No' })).toBeInTheDocument(); + }); + + it('should call delete_payment_method when Yes, remove button is clicked', () => { + render(); + + const yes_remove_button = screen.getByRole('button', { name: 'Yes, remove' }); + userEvent.click(yes_remove_button); + + expect(mock_p2p_advertiser_payment_methods_hooks.delete).toHaveBeenCalled(); + }); + + it('should close modal when No button is clicked', () => { + render(); + + const no_button = screen.getByRole('button', { name: 'No' }); + userEvent.click(no_button); + + expect(mock_modal_manager_context.hideModal).toHaveBeenCalled(); + }); +}); diff --git a/packages/p2p/src/components/modal-manager/modals/delete-payment-method-confirmation-modal/delete-payment-method-confirmation-modal.scss b/packages/p2p/src/components/modal-manager/modals/delete-payment-method-confirmation-modal/delete-payment-method-confirmation-modal.scss new file mode 100644 index 000000000000..b9b69ff06b4e --- /dev/null +++ b/packages/p2p/src/components/modal-manager/modals/delete-payment-method-confirmation-modal/delete-payment-method-confirmation-modal.scss @@ -0,0 +1,5 @@ +.delete-payment-method-confirmation-modal { + display: flex; + word-break: break-all; + padding: 1rem 2.4rem; +} diff --git a/packages/p2p/src/components/modal-manager/modals/delete-payment-method-confirmation-modal/delete-payment-method-confirmation-modal.tsx b/packages/p2p/src/components/modal-manager/modals/delete-payment-method-confirmation-modal/delete-payment-method-confirmation-modal.tsx new file mode 100644 index 000000000000..d26f63b33741 --- /dev/null +++ b/packages/p2p/src/components/modal-manager/modals/delete-payment-method-confirmation-modal/delete-payment-method-confirmation-modal.tsx @@ -0,0 +1,70 @@ +import React from 'react'; +import { Button, Modal, Text } from '@deriv/components'; +import { useP2PAdvertiserPaymentMethods } from '@deriv/hooks'; +import { isMobile } from '@deriv/shared'; +import { Localize, localize } from 'Components/i18next'; +import { useModalManagerContext } from 'Components/modal-manager/modal-manager-context'; + +type TDeletePaymentMethodConfirmationModalProps = { + payment_method_id: number; + payment_method_name: string; +}; + +const DeletePaymentMethodConfirmationModal = ({ + payment_method_id, + payment_method_name, +}: TDeletePaymentMethodConfirmationModalProps) => { + const { hideModal, showModal } = useModalManagerContext(); + const { delete: delete_payment_method, mutation } = useP2PAdvertiserPaymentMethods(); + const { error: mutation_error, status: mutation_status } = mutation; + + const handleDelete = async () => { + delete_payment_method(payment_method_id); + }; + + React.useEffect(() => { + if (mutation_status === 'success') { + hideModal(); + } else if (mutation_status === 'error') { + showModal( + { + key: 'DeletePaymentMethodErrorModal', + props: { + error_message: mutation_error.message, + }, + }, + { should_stack_modal: isMobile() } + ); + } + }, [mutation_error, mutation_status]); + + return ( + + + + } + > + + + + + + + diff --git a/packages/p2p/src/components/modal-manager/modals/quick-add-modal/quick-add-modal.jsx b/packages/p2p/src/components/modal-manager/modals/quick-add-modal/quick-add-modal.jsx index d8240d496536..159dfcab6625 100644 --- a/packages/p2p/src/components/modal-manager/modals/quick-add-modal/quick-add-modal.jsx +++ b/packages/p2p/src/components/modal-manager/modals/quick-add-modal/quick-add-modal.jsx @@ -1,19 +1,22 @@ +import React from 'react'; import classNames from 'classnames'; -import * as React from 'react'; import { Button, Icon, MobileFullPageModal, Modal, Text } from '@deriv/components'; +import { useP2PAdvertiserPaymentMethods } from '@deriv/hooks'; import { isMobile } from '@deriv/shared'; -import { observer } from 'mobx-react-lite'; +import { observer } from '@deriv/stores'; import { localize, Localize } from 'Components/i18next'; -import { buy_sell } from 'Constants/buy-sell'; -import { useStores } from 'Stores'; +import { useModalManagerContext } from 'Components/modal-manager/modal-manager-context'; import AddPaymentMethod from 'Components/my-profile/payment-methods/add-payment-method/add-payment-method.jsx'; -import SellAdPaymentMethodsList from 'Components/my-ads/sell-ad-payment-methods-list.jsx'; import BuyAdPaymentMethodsList from 'Components/my-ads/buy-ad-payment-methods-list.jsx'; -import { useModalManagerContext } from 'Components/modal-manager/modal-manager-context'; +import FilterPaymentMethods from 'Components/my-ads/filter-payment-methods'; +import SellAdPaymentMethodsList from 'Components/my-ads/sell-ad-payment-methods-list.jsx'; +import { buy_sell } from 'Constants/buy-sell'; +import { useStores } from 'Stores'; const QuickAddModal = ({ advert }) => { - const { my_ads_store, my_profile_store } = useStores(); const { is_modal_open, showModal, useSavedState } = useModalManagerContext(); + const { my_ads_store, my_profile_store } = useStores(); + const { data: p2p_advertiser_payment_methods } = useP2PAdvertiserPaymentMethods(); const type = advert ? advert.type : null; @@ -26,22 +29,38 @@ const QuickAddModal = ({ advert }) => { const is_payment_methods_selected = is_sell_ad_add_payment_methods_selected || is_buy_ad_add_payment_methods_selected; + React.useEffect(() => { + const saved_selected_methods = localStorage.getItem('selected_methods'); + if (saved_selected_methods) { + setSelectedMethods(JSON.parse(saved_selected_methods)); + localStorage.removeItem('selected_methods'); + } + my_profile_store.getPaymentMethodsList(); + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + const onClickPaymentMethodCard = payment_method => { - if (!my_ads_store.payment_method_ids.includes(payment_method.ID)) { + if (!my_ads_store.payment_method_ids.includes(payment_method.id)) { if (my_ads_store.payment_method_ids.length < 3) { - my_ads_store.payment_method_ids.push(payment_method.ID); - setSelectedMethods([...selected_methods, payment_method.ID]); + my_ads_store.payment_method_ids.push(payment_method.id); + setSelectedMethods([...selected_methods, payment_method.id]); } } else { my_ads_store.payment_method_ids = my_ads_store.payment_method_ids.filter( - payment_method_id => payment_method_id !== payment_method.ID + payment_method_id => payment_method_id !== payment_method.id ); - setSelectedMethods(selected_methods.filter(i => i !== payment_method.ID)); + setSelectedMethods(selected_methods.filter(i => i !== payment_method.id)); } }; const setShouldCloseAllModals = should_close_all_modals => { - if (is_payment_methods_selected) { + if (my_ads_store.show_filter_payment_methods) { + my_ads_store.setShowFilterPaymentMethods(false); + my_ads_store.setSearchTerm(''); + my_ads_store.setSearchedResults([]); + } else if (is_payment_methods_selected) { + localStorage.setItem('selected_methods', JSON.stringify(selected_methods)); showModal({ key: 'CancelAddPaymentMethodModal', props: { @@ -78,44 +97,58 @@ const QuickAddModal = ({ advert }) => { is_modal_open={is_modal_open} page_header_text={localize('Add payment method')} pageHeaderReturnFn={() => setShouldCloseAllModals(false)} + page_footer_className={classNames({ + 'quick-add-modal--footer': my_ads_store.show_filter_payment_methods, + })} secondary text={localize('Cancel')} - renderPageFooterChildren={() => ( + renderPageFooterChildren={() => + !my_ads_store.show_filter_payment_methods && ( + <> +
- {sortPaymentMethodsListMethods([...my_profile_store.payment_methods_list_methods]).map( - (payment_methods_list_method, key) => { - const payment_methods_list = my_profile_store.advertiser_payment_methods_list.filter( - payment_method => - payment_method.method === payment_methods_list_method.method || - (!independent_categories.includes(payment_method.method) && - payment_methods_list_method.method === 'e_wallet') + {advertiser_payment_methods && + Object.keys(type_to_title_mapper).map(key => { + const current_title = type_to_title_mapper[key]; + const payment_methods = advertiser_payment_methods.filter( + payment_method => payment_method.type === key ); + if (!payment_methods.length) return null; + return ( - {`${payment_methods_list_method.display_name}s`} + {current_title}
- {payment_methods_list.map( - (each_payment_method, each_payment_method_key) => ( - - ) - )} + {payment_methods.map((each_payment_method, each_payment_method_key) => ( + + ))}
); - } - )} + })}
@@ -98,19 +103,19 @@ const PaymentMethodsList = () => { )} >
- {sortPaymentMethodsListMethods([...my_profile_store.payment_methods_list_methods]).map( - (payment_methods_list_method, key) => { - const payment_methods_list = my_profile_store.advertiser_payment_methods_list.filter( - payment_method => - payment_method.method === payment_methods_list_method.method || - (!independent_categories.includes(payment_method.method) && - payment_methods_list_method.method === 'e_wallet') + {advertiser_payment_methods && + Object.keys(type_to_title_mapper).map(key => { + const current_title = type_to_title_mapper[key]; + const payment_methods = advertiser_payment_methods.filter( + payment_method => payment_method.type === key ); + if (!payment_methods.length) return null; + return ( - {`${payment_methods_list_method.display_name}s`} + {current_title} { is_only_horizontal is_scrollbar_hidden > - {payment_methods_list.map( - (each_payment_method, each_payment_method_key) => ( - - ) - )} + {payment_methods.map((each_payment_method, each_payment_method_key) => ( + + ))} ); - } - )} + })}
- general_store.setIsModalOpen(true)} - onUnmount={() => general_store.setIsModalOpen(false)} - title={ - - - - } - > - - - - - - -
+); + +export default PrimaryActionButton; diff --git a/packages/wallets/src/components/PrimaryActionButton/index.ts b/packages/wallets/src/components/PrimaryActionButton/index.ts new file mode 100644 index 000000000000..32af6eae0805 --- /dev/null +++ b/packages/wallets/src/components/PrimaryActionButton/index.ts @@ -0,0 +1 @@ +export { default as PrimaryActionButton } from './PrimaryActionButton'; diff --git a/packages/wallets/src/components/ProgressBar/ProgressBar.scss b/packages/wallets/src/components/ProgressBar/ProgressBar.scss new file mode 100644 index 000000000000..6c0e0ca0efe3 --- /dev/null +++ b/packages/wallets/src/components/ProgressBar/ProgressBar.scss @@ -0,0 +1,24 @@ +.wallets-progress-bar { + display: flex; + justify-content: center; + cursor: pointer; + + &-active { + width: 2.5rem; + height: 0.8rem; + background-color: #ff444f; + border-radius: 1rem; + } + + &-inactive { + width: 0.8rem; + height: 0.8rem; + margin: 0 0.4rem; + border-radius: 50%; + background-color: #c2c2c2; + } + + &-transition { + transition: all 0.24s linear; + } +} diff --git a/packages/wallets/src/components/ProgressBar/ProgressBar.tsx b/packages/wallets/src/components/ProgressBar/ProgressBar.tsx new file mode 100644 index 000000000000..68cc6335929e --- /dev/null +++ b/packages/wallets/src/components/ProgressBar/ProgressBar.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import './ProgressBar.scss'; + +type TProps = { + is_transition?: boolean; + active_index: number; + indexes: Array; + setActiveIndex: React.Dispatch>; +}; + +const ProgressBar: React.FC = ({ is_transition = 'true', active_index, indexes, setActiveIndex }) => { + return ( +
+ {indexes.map(idx => { + const is_active = idx === active_index; + + const bar_classname = is_active ? 'wallets-progress-bar-active' : 'wallets-progress-bar-inactive'; + return ( +
setActiveIndex(idx)} + className={`${bar_classname} ${is_transition ? 'wallets-progress-bar-transition' : ''}`} + /> + ); + })} +
+ ); +}; + +export default ProgressBar; diff --git a/packages/wallets/src/components/ProgressBar/index.ts b/packages/wallets/src/components/ProgressBar/index.ts new file mode 100644 index 000000000000..e072ff53dedd --- /dev/null +++ b/packages/wallets/src/components/ProgressBar/index.ts @@ -0,0 +1 @@ +export { default as ProgressBar } from './ProgressBar'; diff --git a/packages/wallets/src/components/SecondaryActionButton/SecondaryActionButton.scss b/packages/wallets/src/components/SecondaryActionButton/SecondaryActionButton.scss new file mode 100644 index 000000000000..c08995472b8e --- /dev/null +++ b/packages/wallets/src/components/SecondaryActionButton/SecondaryActionButton.scss @@ -0,0 +1,11 @@ +.wallets-secondary-action-button { + display: flex; + height: 32px; + padding: 6px 16px; + justify-content: center; + align-items: center; + border-radius: 4px; + background: rgba(255, 68, 79, 0.16); + border: unset; + cursor: pointer; +} diff --git a/packages/wallets/src/components/SecondaryActionButton/SecondaryActionButton.tsx b/packages/wallets/src/components/SecondaryActionButton/SecondaryActionButton.tsx new file mode 100644 index 000000000000..26c69685b98b --- /dev/null +++ b/packages/wallets/src/components/SecondaryActionButton/SecondaryActionButton.tsx @@ -0,0 +1,10 @@ +import React from 'react'; +import './SecondaryActionButton.scss'; + +const SecondaryActionButton: React.FC> = ({ children, ...rest }) => ( + +); + +export default SecondaryActionButton; diff --git a/packages/wallets/src/components/SecondaryActionButton/index.ts b/packages/wallets/src/components/SecondaryActionButton/index.ts new file mode 100644 index 000000000000..5ab037e89913 --- /dev/null +++ b/packages/wallets/src/components/SecondaryActionButton/index.ts @@ -0,0 +1 @@ +export { default as SecondaryActionButton } from './SecondaryActionButton'; diff --git a/packages/wallets/src/components/Tabs/TabList.scss b/packages/wallets/src/components/Tabs/TabList.scss new file mode 100644 index 000000000000..da0b11e31fe4 --- /dev/null +++ b/packages/wallets/src/components/Tabs/TabList.scss @@ -0,0 +1,31 @@ +.wallets-tabs { + &-list { + display: grid; + grid-auto-columns: 1fr; + position: relative; + background-color: #f2f3f4; + height: 5rem; + margin: 2rem; + border-radius: 7px; + + &-item { + grid-row: 1; + display: flex; + justify-content: center; + align-items: center; + cursor: pointer; + font-size: 1.6rem; + + &--active { + font-weight: bold; + background-color: #ffffff; + margin: 0.6rem; + border-radius: 5px; + } + + &--disabled { + color: #999999; + } + } + } +} diff --git a/packages/wallets/src/components/Tabs/TabList.tsx b/packages/wallets/src/components/Tabs/TabList.tsx new file mode 100644 index 000000000000..364768736e43 --- /dev/null +++ b/packages/wallets/src/components/Tabs/TabList.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { useTabs } from './Tabs'; +import './TabList.scss'; + +type TTabListProps = { + list: string[]; +}; + +export const TabList = ({ list }: TTabListProps) => { + const { active_tab_index, setActiveTabIndex } = useTabs(); + + return ( +
+ {list.map((tab, i) => ( +
setActiveTabIndex(i)} + > + {tab} +
+ ))} +
+ ); +}; diff --git a/packages/wallets/src/components/Tabs/TabPanel.tsx b/packages/wallets/src/components/Tabs/TabPanel.tsx new file mode 100644 index 000000000000..df164d030c85 --- /dev/null +++ b/packages/wallets/src/components/Tabs/TabPanel.tsx @@ -0,0 +1,7 @@ +import React from 'react'; + +const TabPanel = ({ children }: React.PropsWithChildren) => { + return
{children}
; +}; + +export default TabPanel; diff --git a/packages/wallets/src/components/Tabs/TabPanels.tsx b/packages/wallets/src/components/Tabs/TabPanels.tsx new file mode 100644 index 000000000000..d2f0bf51ed63 --- /dev/null +++ b/packages/wallets/src/components/Tabs/TabPanels.tsx @@ -0,0 +1,18 @@ +import React from 'react'; +import { useTabs } from './Tabs'; + +const TabPanels = ({ children }: React.PropsWithChildren) => { + const { active_tab_index } = useTabs(); + + return ( +
+ {React.Children.map(children, (child, index) => { + if (index !== active_tab_index) return undefined; + + return child; + })} +
+ ); +}; + +export default TabPanels; diff --git a/packages/wallets/src/components/Tabs/Tabs.tsx b/packages/wallets/src/components/Tabs/Tabs.tsx new file mode 100644 index 000000000000..62d0b1b331f2 --- /dev/null +++ b/packages/wallets/src/components/Tabs/Tabs.tsx @@ -0,0 +1,34 @@ +import React from 'react'; + +type TTabContext = { + active_tab_index: number; + setActiveTabIndex: React.Dispatch>; +}; + +const TabsContext = React.createContext(null); + +export const useTabs = () => { + const context = React.useContext(TabsContext); + + if (!context) { + throw new Error('Seems you forgot to wrap the components in ""'); + } + + return context; +}; + +type TTabsProps = { + className?: string; +}; + +export const Tabs = ({ children, className }: React.PropsWithChildren) => { + const [active_tab_index, setActiveTabIndex] = React.useState(0); + + return ( + +
{children}
+
+ ); +}; + +export default Tabs; diff --git a/packages/wallets/src/components/Tabs/index.ts b/packages/wallets/src/components/Tabs/index.ts new file mode 100644 index 000000000000..c82393cef446 --- /dev/null +++ b/packages/wallets/src/components/Tabs/index.ts @@ -0,0 +1,6 @@ +import { TabList } from './TabList'; +import TabPanel from './TabPanel'; +import TabPanels from './TabPanels'; +import Tabs from './Tabs'; + +export { TabList, TabPanel, TabPanels, Tabs }; diff --git a/packages/wallets/src/components/TradingAccountCard/TradingAccountCard.scss b/packages/wallets/src/components/TradingAccountCard/TradingAccountCard.scss new file mode 100644 index 000000000000..bf06f25765e3 --- /dev/null +++ b/packages/wallets/src/components/TradingAccountCard/TradingAccountCard.scss @@ -0,0 +1,69 @@ +.wallets-trading-account-card { + display: flex; + align-items: center; + gap: 16px; + height: 88px; + + @include desktop { + $columns: 3; + $grid-last-row-children: '&:nth-child(#{$columns}n+1):nth-last-child(-n+#{$columns})'; + #{$grid-last-row-children} > &__content, + #{$grid-last-row-children} ~ & > &__content { + border-bottom: none; + } + } + + @include mobile { + $columns: 1; + $grid-last-row-children: '&:nth-child(#{$columns}n+1):nth-last-child(-n+#{$columns})'; + #{$grid-last-row-children} > &__content, + #{$grid-last-row-children} ~ & > &__content { + border-bottom: none; + } + } + + &__content { + display: flex; + gap: 16px; + align-items: center; + flex-grow: 1; + height: 100%; + border-bottom: 1px solid var(--system-light-6-hover-background, #e6e9e9); + } + + &__icon { + width: 48px; + height: 48px; + } + + &__details { + flex-grow: 1; + + &-title { + font-weight: bold; + font-size: 1.4rem; + line-height: 20px; + } + + &-description { + font-size: 1.2rem; + line-height: 14px; + + @include mobile { + font-size: 1.4rem; + line-height: 20px; + } + } + } + + &__action { + outline: none; + border: none; + color: var(--system-dark-1-prominent-text, #fff); + font-weight: bold; + padding: 6px 16px; + border-radius: 4px; + background: var(--button-primary-default, #ff444f); + cursor: pointer; + } +} diff --git a/packages/wallets/src/components/TradingAccountCard/TradingAccountCard.tsx b/packages/wallets/src/components/TradingAccountCard/TradingAccountCard.tsx new file mode 100644 index 000000000000..c1ffe625917c --- /dev/null +++ b/packages/wallets/src/components/TradingAccountCard/TradingAccountCard.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import './TradingAccountCard.scss'; + +type TProps = { + leading: () => React.ReactNode; + trailing: () => React.ReactNode; +}; + +const TradingAccountCard: React.FC> = ({ children, leading, trailing }) => { + return ( +
+ {leading?.()} +
{children}
+ {trailing?.()} +
+ ); +}; + +export default TradingAccountCard; diff --git a/packages/wallets/src/components/TradingAccountCard/index.ts b/packages/wallets/src/components/TradingAccountCard/index.ts new file mode 100644 index 000000000000..d4252c34ac01 --- /dev/null +++ b/packages/wallets/src/components/TradingAccountCard/index.ts @@ -0,0 +1 @@ +export { default as TradingAccountCard } from './TradingAccountCard'; diff --git a/packages/wallets/src/components/WalletCard/WalletCard.scss b/packages/wallets/src/components/WalletCard/WalletCard.scss new file mode 100644 index 000000000000..7bff188d7f99 --- /dev/null +++ b/packages/wallets/src/components/WalletCard/WalletCard.scss @@ -0,0 +1,50 @@ +.wallets-card { + display: flex; + flex-direction: column; + align-items: center; + + &__details { + display: flex; + padding: 8px; + min-width: 44.44vw; + flex-direction: column; + justify-content: space-between; + align-items: flex-start; + aspect-ratio: 1.667; + + &__top { + display: flex; + flex-direction: row; + width: 100%; + justify-content: space-between; + } + + &__bottom { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 0.4rem; + + &--virtual { + color: #ffffff; + } + + &__curency { + font-size: 8px; + } + + &__balance { + font-size: 12px; + font-weight: 700; + line-height: 18px; + } + } + + &-landing_company { + display: flex; + justify-content: flex-end; + align-items: flex-start; + gap: 0.4rem; + } + } +} diff --git a/packages/wallets/src/components/WalletCard/WalletCard.tsx b/packages/wallets/src/components/WalletCard/WalletCard.tsx new file mode 100644 index 000000000000..a79a6ce22509 --- /dev/null +++ b/packages/wallets/src/components/WalletCard/WalletCard.tsx @@ -0,0 +1,49 @@ +import React from 'react'; +import { useWalletAccountsList } from '@deriv/api'; +import { WalletGradientBackground } from '../WalletGradientBackground'; +import { WalletListCardBadge } from '../WalletListCardBadge'; +import { WalletListCardIcon } from '../WalletListCardIcon'; +import './WalletCard.scss'; + +type TProps = { + account: ReturnType['data'][number]; +}; + +const WalletCard: React.FC = ({ account }) => { + const { wallet_currency_type, landing_company_name, currency, display_balance, is_virtual, currency_config } = + account || {}; + + const formattedLandingCompany = + landing_company_name === 'virtual' ? 'Demo' : landing_company_name?.toUpperCase() || 'SVG'; + + return ( +
+ +
+
+ +
+ {landing_company_name && ( + + )} +
+
+
+

{currency} Wallet

+

+ {display_balance} {currency} +

+
+
+
+
+ ); +}; + +export default WalletCard; diff --git a/packages/wallets/src/components/WalletCard/index.ts b/packages/wallets/src/components/WalletCard/index.ts new file mode 100644 index 000000000000..1476484016cd --- /dev/null +++ b/packages/wallets/src/components/WalletCard/index.ts @@ -0,0 +1 @@ +export { default as WalletCard } from './WalletCard'; diff --git a/packages/wallets/src/components/WalletGradientBackground/WalletGradientBackground.scss b/packages/wallets/src/components/WalletGradientBackground/WalletGradientBackground.scss new file mode 100644 index 000000000000..ac16ffb409d6 --- /dev/null +++ b/packages/wallets/src/components/WalletGradientBackground/WalletGradientBackground.scss @@ -0,0 +1,1183 @@ +.wallets-gradient { + position: relative; + + &__shine { + position: absolute; + inset: 0; + clip-path: polygon(40% 10%, 104% -6.94%, 92.5% 100%, 28% 100%); + mix-blend-mode: overlay; + opacity: 0.16; + border-top-right-radius: 8px; + background-color: #fff; + } + + &--USD { + &-desktop { + &-card { + &-light { + background: radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(244, 67, 54, 0.24) 0%, + rgba(40, 57, 145, 0.48) 100% + ); + border-radius: 8px; + } + &-dark { + background: radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(244, 67, 54, 0.24) 0%, + rgba(40, 57, 145, 0.48) 100% + ) + #151717; + border-radius: 8px; + } + } + &-header { + &-light { + background: radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(244, 67, 54, 0.4) 0%, + rgba(244, 67, 54, 0.16) 50.52%, + rgba(40, 57, 145, 0.56) 100% + ) + #ffffff; + } + &-dark { + background: radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(244, 67, 54, 0.24) 0%, + rgba(40, 57, 145, 0.48) 100% + ); + } + } + } + + &-mobile { + &-card { + &-light { + background: radial-gradient( + 160% 190% at 0% 100%, + rgba(244, 67, 54, 0.5) 0%, + rgba(244, 67, 54, 0.4) 50.52%, + rgba(40, 57, 145, 0.56) 100% + ) + #ffffff; + border-radius: 8px; + } + &-dark { + background: radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(244, 67, 54, 0.4) 0%, + rgba(244, 67, 54, 0.16) 50.52%, + rgba(40, 57, 145, 0.56) 100% + ) + #151717; + border-radius: 8px; + } + } + &-header { + &-light { + background: radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(244, 67, 54, 0.4) 0%, + rgba(244, 67, 54, 0.16) 50.52%, + rgba(40, 57, 145, 0.56) 100% + ) + #ffffff; + } + &-dark { + background: radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(244, 67, 54, 0.24) 0%, + rgba(40, 57, 145, 0.48) 100% + ); + } + } + } + } + &--AUD { + &-desktop { + &-card { + &-light { + background: radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(13, 180, 61, 0.24) 0%, + rgba(255, 205, 0, 0.48) 100% + ) + #ffffff; + border-radius: 8px; + } + &-dark { + background: radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(13, 180, 61, 0.24) 0%, + rgba(255, 205, 0, 0.48) 100% + ) + #151717; + border-radius: 8px; + } + } + + &-header { + &-light { + background: radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(13, 180, 61, 0.4) 0%, + rgba(13, 180, 61, 0.16) 50.52%, + rgba(255, 205, 0, 0.56) 100% + ) + #ffffff; + } + &-dark { + background: radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(13, 180, 61, 0.4) 0%, + rgba(13, 180, 61, 0.16) 50.52%, + rgba(255, 205, 0, 0.56) 100% + ) + #151717; + } + } + } + &-mobile { + &-card { + &-light { + background: radial-gradient( + 160% 190% at 0% 100%, + rgba(13, 180, 61, 0.5) 0%, + rgba(13, 180, 61, 0.4) 50.52%, + rgba(255, 205, 0, 0.48) 100% + ) + #ffffff; + border-radius: 8px; + } + &-dark { + background: radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(13, 180, 61, 0.24) 0%, + rgba(255, 205, 0, 0.48) 100% + ) + #151717; + border-radius: 8px; + } + } + + &-header { + &-light { + background: radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(13, 180, 61, 0.4) 0%, + rgba(13, 180, 61, 0.16) 50.52%, + rgba(255, 205, 0, 0.56) 100% + ) + #ffffff; + } + &-dark { + background: radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(13, 180, 61, 0.4) 0%, + rgba(13, 180, 61, 0.16) 50.52%, + rgba(255, 205, 0, 0.56) 100% + ) + #151717; + } + } + } + } + &--EUR { + &-desktop { + &-card { + &-light { + background: radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(40, 57, 145, 0.24) 0%, + rgba(248, 209, 46, 0.48) 100% + ) + #ffffff; + border-radius: 8px; + } + &-dark { + background: radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(40, 57, 145, 0.24) 0%, + rgba(248, 209, 46, 0.48) 100% + ) + #151717; + border-radius: 8px; + } + } + + &-header { + &-light { + background: radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(40, 57, 145, 0.4) 0%, + rgba(40, 57, 145, 0.16) 50.52%, + rgba(248, 209, 46, 0.56) 100% + ) + #ffffff; + } + &-dark { + background: radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(40, 57, 145, 0.4) 0%, + rgba(40, 57, 145, 0.16) 50.52%, + rgba(248, 209, 46, 0.56) 100% + ) + #151717; + } + } + } + &-mobile { + &-card { + &-light { + background: radial-gradient( + 160% 190% at 0% 100%, + rgba(40, 57, 145, 0.5) 0%, + rgba(40, 57, 145, 0.4) 50.52%, + rgba(248, 209, 46, 0.48) 100% + ) + #ffffff; + border-radius: 8px; + } + &-dark { + background: radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(40, 57, 145, 0.24) 0%, + rgba(248, 209, 46, 0.48) 100% + ) + #151717; + border-radius: 8px; + } + } + + &-header { + &-light { + background: radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(40, 57, 145, 0.4) 0%, + rgba(40, 57, 145, 0.16) 50.52%, + rgba(248, 209, 46, 0.56) 100% + ) + #ffffff; + } + &-dark { + background: radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(40, 57, 145, 0.4) 0%, + rgba(40, 57, 145, 0.16) 50.52%, + rgba(248, 209, 46, 0.56) 100% + ) + #151717; + } + } + } + } + &--GBP { + &-desktop { + &-card { + &-light { + background: radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(40, 57, 145, 0.24) 0%, + rgba(40, 57, 145, 0.24) 0.01%, + rgba(244, 67, 54, 0.48) 100% + ) + #ffffff; + border-radius: 8px; + } + &-dark { + background: radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(40, 57, 145, 0.24) 0%, + rgba(40, 57, 145, 0.24) 0.01%, + rgba(244, 67, 54, 0.48) 100% + ) + #151717; + border-radius: 8px; + } + } + + &-header { + &-light { + background: radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(40, 57, 145, 0.4) 0%, + rgba(40, 57, 145, 0.16) 50.52%, + rgba(244, 67, 54, 0.56) 100% + ) + #ffffff; + } + &-dark { + background: radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(40, 57, 145, 0.4) 0%, + rgba(40, 57, 145, 0.16) 50.52%, + rgba(244, 67, 54, 0.56) 100% + ) + #151717; + } + } + } + &-mobile { + &-card { + &-light { + background: radial-gradient( + 160% 190% at 0% 100%, + rgba(40, 57, 145, 0.5) 0%, + rgba(40, 57, 145, 0.4) 50.52%, + rgba(244, 67, 54, 0.48) 100% + ) + #ffffff; + border-radius: 8px; + } + &-dark { + background: radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(40, 57, 145, 0.24) 0%, + rgba(40, 57, 145, 0.24) 0.01%, + rgba(244, 67, 54, 0.48) 100% + ) + #151717; + border-radius: 8px; + } + } + + &-header { + &-light { + background: radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(40, 57, 145, 0.4) 0%, + rgba(40, 57, 145, 0.16) 50.52%, + rgba(244, 67, 54, 0.56) 100% + ) + #ffffff; + } + &-dark { + background: radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(40, 57, 145, 0.4) 0%, + rgba(40, 57, 145, 0.16) 50.52%, + rgba(244, 67, 54, 0.56) 100% + ) + #151717; + } + } + } + } + &--BTC { + &-desktop { + &-card { + &-light { + background: radial-gradient( + 100% 277.78% at 0% 100%, + rgba(247, 147, 27, 0.24) 0%, + rgba(247, 199, 27, 0.477) 99.99% + ) + #ffffff; + border-radius: 8px; + } + &-dark { + background: radial-gradient( + 100% 277.78% at 0% 100%, + rgba(247, 147, 27, 0.24) 0%, + rgba(247, 199, 27, 0.477) 99.99%, + rgba(255, 100, 68, 0.48) 100% + ) + #151717; + border-radius: 8px; + } + } + + &-header { + &-light { + background: radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(247, 147, 27, 0.4) 0%, + rgba(247, 147, 27, 0.16) 50.52%, + rgba(247, 199, 27, 0.4) 100% + ) + #ffffff; + } + &-dark { + background: radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(247, 147, 27, 0.4) 0%, + rgba(247, 147, 27, 0.16) 50.52%, + rgba(247, 199, 27, 0.4) 100% + ) + #151717; + } + } + } + &-mobile { + &-card { + &-light { + background: radial-gradient( + 160% 190% at 0% 100%, + rgba(247, 147, 27, 0.5) 0%, + rgba(247, 147, 27, 0.4) 50.52%, + rgba(247, 199, 27, 0.477) 99.99% + ) + #ffffff; + border-radius: 8px; + } + &-dark { + background: radial-gradient( + 100% 277.78% at 0% 100%, + rgba(247, 147, 27, 0.24) 0%, + rgba(247, 199, 27, 0.477) 99.99%, + rgba(255, 100, 68, 0.48) 100% + ) + #151717; + border-radius: 8px; + } + } + + &-header { + &-light { + background: radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(247, 147, 27, 0.4) 0%, + rgba(247, 147, 27, 0.16) 50.52%, + rgba(247, 199, 27, 0.4) 100% + ) + #ffffff; + } + &-dark { + background: radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(247, 147, 27, 0.4) 0%, + rgba(247, 147, 27, 0.16) 50.52%, + rgba(247, 199, 27, 0.4) 100% + ) + #151717; + } + } + } + } + &--ETH { + &-desktop { + &-card { + &-light { + background: radial-gradient( + 100% 277.78% at 0% 100%, + rgba(82, 86, 127, 0.24) 0%, + rgba(130, 140, 173, 0.48) 100% + ) + #ffffff; + border-radius: 8px; + } + &-dark { + background: radial-gradient( + 100% 277.78% at 0% 100%, + rgba(82, 86, 127, 0.24) 0%, + rgba(130, 140, 173, 0.48) 100% + ) + #151717; + border-radius: 8px; + } + } + + &-header { + &-light { + background: radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(82, 86, 127, 0.4) 0%, + rgba(82, 86, 127, 0.16) 50.52%, + rgba(130, 140, 173, 0.4) 100% + ) + #ffffff; + } + &-dark { + background: radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(82, 86, 127, 0.4) 0%, + rgba(82, 86, 127, 0.16) 50.52%, + rgba(130, 140, 173, 0.4) 100% + ) + #151717; + } + } + } + &-mobile { + &-card { + &-light { + background: radial-gradient( + 160% 190% at 0% 100%, + rgba(82, 86, 127, 0.4) 0%, + rgba(82, 86, 127, 0.5) 50.52%, + rgba(130, 140, 173, 0.48) 100% + ) + #ffffff; + border-radius: 8px; + } + &-dark { + background: radial-gradient( + 100% 277.78% at 0% 100%, + rgba(82, 86, 127, 0.24) 0%, + rgba(130, 140, 173, 0.48) 100% + ) + #151717; + border-radius: 8px; + } + } + + &-header { + &-light { + background: radial-gradient( + 100% 277.78% at 0% 100%, + rgba(82, 86, 127, 0.24) 0%, + rgba(130, 140, 173, 0.48) 100% + ) + #ffffff; + } + &-dark { + background: radial-gradient( + 100% 277.78% at 0% 100%, + rgba(82, 86, 127, 0.24) 0%, + rgba(130, 140, 173, 0.48) 100% + ) + #151717; + } + } + } + } + &--LTC { + &-desktop { + &-card { + &-light { + background: radial-gradient( + 100% 277.78% at 0% 100%, + rgba(165, 168, 169, 0.24) 0%, + rgba(193, 204, 207, 0.48) 100% + ) + #ffffff; + border-radius: 8px; + } + &-dark { + background: radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(165, 168, 169, 0.4) 0%, + rgba(165, 168, 169, 0.16) 50.52%, + rgba(193, 204, 207, 0.4) 100% + ) + #151717; + border-radius: 8px; + } + } + + &-header { + &-light { + background: radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(165, 168, 169, 0.4) 0%, + rgba(165, 168, 169, 0.16) 50.52%, + rgba(193, 204, 207, 0.4) 100% + ) + #ffffff; + } + &-dark { + background: radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(165, 168, 169, 0.4) 0%, + rgba(165, 168, 169, 0.16) 50.52%, + rgba(193, 204, 207, 0.4) 100% + ) + #151717; + } + } + } + &-mobile { + &-card { + &-light { + background: radial-gradient( + 160% 190% at 0% 100%, + rgba(165, 168, 169, 0.4) 0%, + rgba(165, 168, 169, 0.4) 50.52%, + rgba(193, 204, 207, 0.48) 100% + ) + #ffffff; + border-radius: 8px; + } + &-dark { + background: radial-gradient( + 100% 277.78% at 0% 100%, + rgba(165, 168, 169, 0.24) 0%, + rgba(193, 204, 207, 0.48) 100% + ) + #151717; + border-radius: 8px; + } + } + + &-header { + &-light { + background: radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(165, 168, 169, 0.4) 0%, + rgba(165, 168, 169, 0.16) 50.52%, + rgba(193, 204, 207, 0.4) 100% + ) + #ffffff; + } + &-dark { + background: radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(165, 168, 169, 0.4) 0%, + rgba(165, 168, 169, 0.16) 50.52%, + rgba(193, 204, 207, 0.4) 100% + ) + #151717; + } + } + } + } + &--UST { + &-desktop { + &-card { + &-light { + background: radial-gradient( + 100% 277.78% at 0% 100%, + rgba(0, 147, 147, 0.24) 0%, + rgba(0, 147, 147, 0.48) 100% + ) + #ffffff; + border-radius: 8px; + } + &-dark { + background: radial-gradient( + 100% 277.78% at 0% 100%, + rgba(0, 147, 147, 0.24) 0%, + rgba(0, 147, 147, 0.48) 100% + ) + #151717; + border-radius: 8px; + } + } + + &-header { + &-light { + background: radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(0, 147, 147, 0.4) 0%, + rgba(0, 147, 147, 0.16) 50.52%, + rgba(4, 217, 217, 0.4) 100% + ) + #ffffff; + } + &-dark { + background: radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(0, 147, 147, 0.4) 0%, + rgba(0, 147, 147, 0.16) 50.52%, + rgba(4, 217, 217, 0.4) 100% + ) + #151717; + } + } + } + &-mobile { + &-card { + &-light { + background: radial-gradient( + 160% 190% at 0% 100%, + rgba(0, 147, 147, 0.4) 0%, + rgba(0, 147, 147, 0.5) 50.52%, + rgba(0, 147, 147, 0.48) 100% + ) + #ffffff; + border-radius: 8px; + } + &-dark { + background: radial-gradient( + 100% 277.78% at 0% 100%, + rgba(0, 147, 147, 0.24) 0%, + rgba(0, 147, 147, 0.48) 100% + ) + #151717; + border-radius: 8px; + } + } + + &-header { + &-light { + background: radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(0, 147, 147, 0.4) 0%, + rgba(0, 147, 147, 0.16) 50.52%, + rgba(4, 217, 217, 0.4) 100% + ) + #ffffff; + } + &-dark { + background: radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(0, 147, 147, 0.4) 0%, + rgba(0, 147, 147, 0.16) 50.52%, + rgba(4, 217, 217, 0.4) 100% + ) + #151717; + } + } + } + } + &--USDT { + &-desktop { + &-card { + &-light { + background: radial-gradient( + 100% 277.78% at 0% 100%, + rgba(0, 147, 147, 0.24) 0%, + rgba(0, 147, 147, 0.48) 100% + ) + #ffffff; + border-radius: 8px; + } + &-dark { + background: radial-gradient( + 100% 277.78% at 0% 100%, + rgba(0, 147, 147, 0.24) 0%, + rgba(0, 147, 147, 0.48) 100% + ) + #151717; + border-radius: 8px; + } + } + + &-header { + &-light { + background: radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(0, 147, 147, 0.4) 0%, + rgba(0, 147, 147, 0.16) 50.52%, + rgba(4, 217, 217, 0.4) 100% + ) + #ffffff; + } + &-dark { + background: radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(0, 147, 147, 0.4) 0%, + rgba(0, 147, 147, 0.16) 50.52%, + rgba(4, 217, 217, 0.4) 100% + ) + #151717; + } + } + } + &-mobile { + &-card { + &-light { + background: radial-gradient( + 160% 190% at 0% 100%, + rgba(0, 147, 147, 0.4) 0%, + rgba(0, 147, 147, 0.5) 50.52%, + rgba(0, 147, 147, 0.48) 100% + ) + #ffffff; + } + &-dark { + background: radial-gradient( + 100% 277.78% at 0% 100%, + rgba(0, 147, 147, 0.24) 0%, + rgba(0, 147, 147, 0.48) 100% + ) + #151717; + } + } + + &-header { + &-light { + background: radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(0, 147, 147, 0.4) 0%, + rgba(0, 147, 147, 0.16) 50.52%, + rgba(4, 217, 217, 0.4) 100% + ) + #ffffff; + border-radius: 8px; + } + &-dark { + background: radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(0, 147, 147, 0.4) 0%, + rgba(0, 147, 147, 0.16) 50.52%, + rgba(4, 217, 217, 0.4) 100% + ) + #151717; + border-radius: 8px; + } + } + } + } + &--tUSDT { + &-desktop { + &-card { + &-light { + background: radial-gradient( + 100% 277.78% at 0% 100%, + rgba(0, 147, 147, 0.24) 0%, + rgba(0, 147, 147, 0.48) 100% + ) + #ffffff; + border-radius: 8px; + } + &-dark { + background: radial-gradient( + 100% 277.78% at 0% 100%, + rgba(0, 147, 147, 0.24) 0%, + rgba(0, 147, 147, 0.48) 100% + ) + #151717; + border-radius: 8px; + } + } + + &-header { + &-light { + background: radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(0, 147, 147, 0.4) 0%, + rgba(0, 147, 147, 0.16) 50.52%, + rgba(4, 217, 217, 0.4) 100% + ) + #ffffff; + } + &-dark { + background: radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(0, 147, 147, 0.4) 0%, + rgba(0, 147, 147, 0.16) 50.52%, + rgba(4, 217, 217, 0.4) 100% + ) + #151717; + } + } + } + &-mobile { + &-card { + &-light { + background: radial-gradient( + 160% 190% at 0% 100%, + rgba(0, 147, 147, 0.4) 0%, + rgba(0, 147, 147, 0.5) 50.52%, + rgba(0, 147, 147, 0.48) 100% + ) + #ffffff; + border-radius: 8px; + } + &-dark { + background: radial-gradient( + 100% 277.78% at 0% 100%, + rgba(0, 147, 147, 0.24) 0%, + rgba(0, 147, 147, 0.48) 100% + ) + #151717; + border-radius: 8px; + } + } + + &-header { + &-light { + background: radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(0, 147, 147, 0.4) 0%, + rgba(0, 147, 147, 0.16) 50.52%, + rgba(4, 217, 217, 0.4) 100% + ) + #ffffff; + } + &-dark { + background: radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(0, 147, 147, 0.4) 0%, + rgba(0, 147, 147, 0.16) 50.52%, + rgba(4, 217, 217, 0.4) 100% + ) + #151717; + } + } + } + } + &--eUSDT { + &-desktop { + &-card { + &-light { + background: radial-gradient( + 100% 277.78% at 0% 100%, + rgba(0, 147, 147, 0.24) 0%, + rgba(0, 147, 147, 0.48) 100% + ) + #ffffff; + border-radius: 8px; + } + &-dark { + background: radial-gradient( + 100% 277.78% at 0% 100%, + rgba(0, 147, 147, 0.24) 0%, + rgba(0, 147, 147, 0.48) 100% + ) + #151717; + border-radius: 8px; + } + } + + &-header { + &-light { + background: radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(0, 147, 147, 0.4) 0%, + rgba(0, 147, 147, 0.16) 50.52%, + rgba(4, 217, 217, 0.4) 100% + ) + #ffffff; + } + &-dark { + background: radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(0, 147, 147, 0.4) 0%, + rgba(0, 147, 147, 0.16) 50.52%, + rgba(4, 217, 217, 0.4) 100% + ) + #151717; + } + } + } + &-mobile { + &-card { + &-light { + background: radial-gradient( + 160% 190% at 0% 100%, + rgba(0, 147, 147, 0.4) 0%, + rgba(0, 147, 147, 0.5) 50.52%, + rgba(0, 147, 147, 0.48) 100% + ) + #ffffff; + border-radius: 8px; + } + &-dark { + background: radial-gradient( + 100% 277.78% at 0% 100%, + rgba(0, 147, 147, 0.24) 0%, + rgba(0, 147, 147, 0.48) 100% + ) + #151717; + border-radius: 8px; + } + } + + &-header { + &-light { + background: radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(0, 147, 147, 0.4) 0%, + rgba(0, 147, 147, 0.16) 50.52%, + rgba(4, 217, 217, 0.4) 100% + ) + #ffffff; + } + &-dark { + background: radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(0, 147, 147, 0.4) 0%, + rgba(0, 147, 147, 0.16) 50.52%, + rgba(4, 217, 217, 0.4) 100% + ) + #151717; + } + } + } + } + &--USDC { + &-desktop { + &-card { + &-light { + background: radial-gradient( + 100% 277.78% at 0% 100%, + rgba(39, 117, 202, 0.24) 0%, + rgba(34, 76, 225, 0.48) 100% + ) + #ffffff; + border-radius: 8px; + } + &-dark { + background: radial-gradient( + 100% 277.78% at 0% 100%, + rgba(39, 117, 202, 0.24) 0%, + rgba(34, 76, 225, 0.48) 100% + ) + #151717; + border-radius: 8px; + } + } + + &-header { + &-light { + background: radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(39, 117, 202, 0.4) 0%, + rgba(39, 117, 202, 0.16) 50.52%, + rgba(34, 76, 225, 0.4) 100% + ) + #ffffff; + } + &-dark { + background: radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(39, 117, 202, 0.4) 0%, + rgba(39, 117, 202, 0.16) 50.52%, + rgba(34, 76, 225, 0.4) 100% + ) + #151717; + } + } + } + &-mobile { + &-card { + &-light { + background: radial-gradient( + 160% 190% at 0% 100%, + rgba(39, 117, 202, 0.4) 0%, + rgba(39, 117, 202, 0.5) 50.52%, + rgba(34, 76, 225, 0.48) 100% + ) + #ffffff; + border-radius: 8px; + } + &-dark { + background: radial-gradient( + 100% 277.78% at 0% 100%, + rgba(39, 117, 202, 0.24) 0%, + rgba(34, 76, 225, 0.48) 100% + ) + #151717; + border-radius: 8px; + } + } + + &-header { + &-light { + background: radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(39, 117, 202, 0.4) 0%, + rgba(39, 117, 202, 0.16) 50.52%, + rgba(34, 76, 225, 0.4) 100% + ) + #ffffff; + } + &-dark { + background: radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(39, 117, 202, 0.4) 0%, + rgba(39, 117, 202, 0.16) 50.52%, + rgba(34, 76, 225, 0.4) 100% + ) + #151717; + } + } + } + } + &--demo { + &-desktop { + &-card { + &-light { + background: radial-gradient( + 100% 277.78% at 0% 100%, + rgba(255, 100, 68, 0.24) 0%, + rgba(255, 68, 79, 0.48) 100% + ) + #212329; + border-radius: 8px; + } + &-dark { + background: radial-gradient( + 100% 277.78% at 0% 100%, + rgba(255, 100, 68, 0.24) 0%, + rgba(255, 68, 79, 0.48) 100% + ) + #fbdddd; + border-radius: 8px; + } + } + + &-header { + &-light { + background: radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(255, 100, 68, 0.4) 0%, + rgba(255, 100, 68, 0.16) 50.52%, + rgba(255, 68, 79, 0.4) 100% + ) + #212329; + } + &-dark { + background: radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(255, 100, 68, 0.4) 0%, + rgba(255, 100, 68, 0.16) 50.52%, + rgba(255, 68, 79, 0.4) 100% + ) + #fbdddd; + } + } + } + &-mobile { + &-card { + &-light { + background: radial-gradient( + 100% 277.78% at 0% 100%, + rgba(255, 100, 68, 0.24) 0%, + rgba(255, 68, 79, 0.68) 100% + ) + #212329; + border-radius: 8px; + &:before { + content: ''; + display: block; + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + background-image: url('../../public//images/wallet-demo-bg-light.svg'); + background-repeat: repeat; + background-size: 70px; + mix-blend-mode: overlay; + opacity: 0.24; + } + } + &-dark { + background: radial-gradient( + 100% 277.78% at 0% 100%, + rgba(255, 100, 68, 0.24) 0%, + rgba(255, 68, 79, 0.48) 100% + ) + #fbdddd; + border-radius: 8px; + } + } + + &-header { + &-light { + background: radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(255, 100, 68, 0.4) 0%, + rgba(255, 100, 68, 0.16) 50.52%, + rgba(255, 68, 79, 0.4) 100% + ) + #212329; + } + &-dark { + background: radial-gradient( + 100% 4130.74% at 0% 100%, + rgba(255, 100, 68, 0.4) 0%, + rgba(255, 100, 68, 0.16) 50.52%, + rgba(255, 68, 79, 0.4) 100% + ) + #fbdddd; + } + } + } + } +} diff --git a/packages/wallets/src/components/WalletGradientBackground/WalletGradientBackground.tsx b/packages/wallets/src/components/WalletGradientBackground/WalletGradientBackground.tsx new file mode 100644 index 000000000000..85326e6a8fec --- /dev/null +++ b/packages/wallets/src/components/WalletGradientBackground/WalletGradientBackground.tsx @@ -0,0 +1,35 @@ +import React from 'react'; +import './WalletGradientBackground.scss'; + +type WalletGradientBackground = { + children: React.ReactNode; + currency: string; + device?: 'desktop' | 'mobile'; + has_shine?: boolean; + is_demo?: boolean; + theme?: 'dark' | 'light'; + type?: 'card' | 'header'; +}; + +const WalletGradientBackground: React.FC = ({ + has_shine = false, + is_demo = false, + currency, + theme = 'light', + type = 'card', + device = 'desktop', + children, +}) => { + const className = is_demo + ? `wallets-gradient--demo-${device}-${type}-${theme}` + : `wallets-gradient--${currency}-${device}-${type}-${theme}`; + + return ( +
+ {has_shine && !is_demo && } + {children} +
+ ); +}; + +export default WalletGradientBackground; diff --git a/packages/wallets/src/components/WalletGradientBackground/index.ts b/packages/wallets/src/components/WalletGradientBackground/index.ts new file mode 100644 index 000000000000..ad03b517aae2 --- /dev/null +++ b/packages/wallets/src/components/WalletGradientBackground/index.ts @@ -0,0 +1 @@ +export { default as WalletGradientBackground } from './WalletGradientBackground'; diff --git a/packages/wallets/src/components/WalletListCard/WalletListCard.scss b/packages/wallets/src/components/WalletListCard/WalletListCard.scss index 58e49881da52..7e184a5016fa 100644 --- a/packages/wallets/src/components/WalletListCard/WalletListCard.scss +++ b/packages/wallets/src/components/WalletListCard/WalletListCard.scss @@ -2,11 +2,11 @@ &__card_container { flex: 1; display: flex; - padding: 24px; align-items: flex-start; width: 100%; border-radius: 16px; - background: var(--system-light-8-primary-background, #fff); + background: none; + z-index: 10; } &__content { @@ -25,9 +25,16 @@ gap: 24px; align-self: stretch; border-radius: 16px; - } - &__dropdown { - cursor: pointer; + &-icon { + display: flex; + justify-content: center; + align-items: center; + flex-shrink: 0; + border-radius: 4px; + padding: 10px 16px; + min-width: 122px; + min-height: 75px; + } } } diff --git a/packages/wallets/src/components/WalletListCard/WalletListCard.tsx b/packages/wallets/src/components/WalletListCard/WalletListCard.tsx index 2c70de9d6fe9..17a40b015f2a 100644 --- a/packages/wallets/src/components/WalletListCard/WalletListCard.tsx +++ b/packages/wallets/src/components/WalletListCard/WalletListCard.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { useWalletAccountsList } from '@deriv/api'; -import IcDropdown from '../../public/images/ic-dropdown.svg'; +import WalletGradientBackground from '../WalletGradientBackground/WalletGradientBackground'; import WalletListCardIBalance from '../WalletListCardIBalance/WalletListCardIBalance'; import WalletListCardIcon from '../WalletListCardIcon/WalletListCardIcon'; import WalletListCardIDetails from '../WalletListCardIDetails/WalletListCardIDetails'; @@ -15,13 +15,19 @@ const WalletListCard: React.FC = ({ account }) => {
- + +
+ +
+
+
-
- -
); diff --git a/packages/wallets/src/components/WalletListCardBadge/WalletListCardBadge.scss b/packages/wallets/src/components/WalletListCardBadge/WalletListCardBadge.scss index 9330413cf15a..410efe5514d4 100644 --- a/packages/wallets/src/components/WalletListCardBadge/WalletListCardBadge.scss +++ b/packages/wallets/src/components/WalletListCardBadge/WalletListCardBadge.scss @@ -7,6 +7,17 @@ gap: 4px; border-radius: 2px; border: 1px solid var(--system-light-1-prominent-text, #333); + + &--demo { + display: flex; + padding: 0px 4px; + justify-content: center; + align-items: center; + gap: 4px; + border-radius: 2px; + background: var(--status-light-information, #377cfc); + color: #ffffff; + } } &__name { color: var(--system-light-1-prominent-text, #333); diff --git a/packages/wallets/src/components/WalletListCardBadge/WalletListCardBadge.tsx b/packages/wallets/src/components/WalletListCardBadge/WalletListCardBadge.tsx index 5fbdd4acefc5..e8e6ed1435d2 100644 --- a/packages/wallets/src/components/WalletListCardBadge/WalletListCardBadge.tsx +++ b/packages/wallets/src/components/WalletListCardBadge/WalletListCardBadge.tsx @@ -3,13 +3,20 @@ import './WalletListCardBadge.scss'; type TProps = { label: string; + is_demo?: boolean; }; -const WalletListCardBadge: React.FC = ({ label }) => { +const WalletListCardBadge: React.FC = ({ label, is_demo }) => { + const className = is_demo ? 'wallets-list-card__badge--demo' : 'wallets-list-card__badge'; + + const labelStyle: React.CSSProperties = { + color: is_demo ? 'white' : 'black', + }; + return ( -
+
-

{label.toUpperCase()}

+

{label}

); diff --git a/packages/wallets/src/components/WalletListCardIActions/WalletListCardIActions.scss b/packages/wallets/src/components/WalletListCardIActions/WalletListCardIActions.scss index 6d206c5d8182..a36a875da9ae 100644 --- a/packages/wallets/src/components/WalletListCardIActions/WalletListCardIActions.scss +++ b/packages/wallets/src/components/WalletListCardIActions/WalletListCardIActions.scss @@ -14,5 +14,54 @@ gap: 8px; border-radius: 64px; border: 1px solid var(--system-light-3-less-prominent-text, #999); + background: none; + } +} + +.wallets-mobile-actions { + display: flex; + flex-direction: row; + align-items: center; + cursor: pointer; + min-width: 5.6rem; + + &__container { + display: flex; + padding: 0rem 1.6rem; + justify-content: center; + align-items: center; + gap: 0.8rem; + align-self: stretch; + } + + &-content { + display: flex; + width: 7rem; + flex-direction: column; + align-items: center; + gap: 0.4rem; + + &-icon { + display: flex; + width: 3.5rem; + height: 3.5rem; + padding: 0.8rem; + justify-content: center; + align-items: center; + border-radius: 16px; + border: 1px solid var(--system-light-5-active-background, #d6dadb); + } + + &-text { + color: var(--system-light-1-prominent-text, #333); + text-align: center; + + /* mobile/extra small/XS - regular */ + font-family: 'IBM Plex Sans'; + font-size: 1rem; + font-style: normal; + font-weight: 400; + line-height: 1.2rem; /* 150% */ + } } } diff --git a/packages/wallets/src/components/WalletListCardIActions/WalletListCardIActions.tsx b/packages/wallets/src/components/WalletListCardIActions/WalletListCardIActions.tsx index 596dc18b8d86..83ca3fab4eb3 100644 --- a/packages/wallets/src/components/WalletListCardIActions/WalletListCardIActions.tsx +++ b/packages/wallets/src/components/WalletListCardIActions/WalletListCardIActions.tsx @@ -1,5 +1,6 @@ import React from 'react'; -import { useWalletAccountsList } from '@deriv/api'; +import { useActiveWalletAccount, useWalletAccountsList } from '@deriv/api'; +import useDevice from '../../hooks/useDevice'; import IcCashierAdd from '../../public/images/ic-cashier-deposit.svg'; import IcCashierStatement from '../../public/images/ic-cashier-statement.svg'; import IcCashierTransfer from '../../public/images/ic-cashier-transfer.svg'; @@ -40,15 +41,44 @@ const getWalletHeaderButtons = (is_demo: boolean, handleAction?: () => void) => }; type TProps = { - account: NonNullable['data']>[number]; + account?: NonNullable['data']>[number]; }; const WalletListCardIActions: React.FC = ({ account }) => { - const is_demo = account.is_virtual; + const { is_mobile } = useDevice(); + const { data: active_wallet } = useActiveWalletAccount(); + const is_demo = !!active_wallet?.is_virtual; + + if (is_mobile) + return ( +
+
+ {getWalletHeaderButtons(is_demo).map(button => ( + +
+ +
{button.text}
+
+
+ ))} +
+
+ ); + return (
- {getWalletHeaderButtons(is_demo).map(button => ( - ))} diff --git a/packages/wallets/src/components/WalletListCardIDetails/WalletListCardIDetails.tsx b/packages/wallets/src/components/WalletListCardIDetails/WalletListCardIDetails.tsx index 35056470084f..016662f4eb1b 100644 --- a/packages/wallets/src/components/WalletListCardIDetails/WalletListCardIDetails.tsx +++ b/packages/wallets/src/components/WalletListCardIDetails/WalletListCardIDetails.tsx @@ -10,13 +10,15 @@ type TProps = { }; const WalletListCardIDetails: React.FC = ({ account }) => { - const { currency_config, landing_company_name } = account; + const { currency_config, landing_company_name, is_virtual } = account; return (
{currency_config?.display_code && } - {landing_company_name && } + {landing_company_name && !is_virtual && ( + + )}
diff --git a/packages/wallets/src/components/WalletListCardIcon/WalletListCardIcon.scss b/packages/wallets/src/components/WalletListCardIcon/WalletListCardIcon.scss deleted file mode 100644 index 211b5324d427..000000000000 --- a/packages/wallets/src/components/WalletListCardIcon/WalletListCardIcon.scss +++ /dev/null @@ -1,17 +0,0 @@ -.wallets-card { - &__icon { - display: flex; - width: 128px; - height: 80px; - justify-content: center; - align-items: center; - flex-shrink: 0; - border-radius: 4px; - background: linear-gradient( - 90deg, - rgba(131, 58, 180, 1) 0%, - rgba(253, 29, 29, 1) 50%, - rgba(252, 176, 69, 1) 100% - ); - } -} diff --git a/packages/wallets/src/components/WalletListCardIcon/WalletListCardIcon.tsx b/packages/wallets/src/components/WalletListCardIcon/WalletListCardIcon.tsx index 39eb36da3405..a71ce52bea2d 100644 --- a/packages/wallets/src/components/WalletListCardIcon/WalletListCardIcon.tsx +++ b/packages/wallets/src/components/WalletListCardIcon/WalletListCardIcon.tsx @@ -1,12 +1,53 @@ import React from 'react'; -import './WalletListCardIcon.scss'; - -const WalletListCardIcon: React.FC = () => { - return ( -
-

Icon (Placeholder)

-
- ); +import useDevice from '../../hooks/useDevice'; +import Bitcion from '../../public/images/bitcion.svg'; +import Demo from '../../public/images/demo.svg'; +import ETH from '../../public/images/eth.svg'; +import EUR from '../../public/images/eur.svg'; +import Tether from '../../public/images/eusdt.svg'; +import GBP from '../../public/images/gbp.svg'; +import LTC from '../../public/images/ltc.svg'; +import USD from '../../public/images/usd.svg'; +import USDC from '../../public/images/usdc.svg'; + +const type_to_icon_mapper = { + USD, + EUR, + GBP, + BTC: Bitcion, + USDC, + ETH, + LTC, + UST: Tether, + eUSDT: Tether, + Demo, +}; + +const type_to_size_mapper = { + USD: { mobile: 32, desktop: 48 }, + EUR: { mobile: 32, desktop: 48 }, + GBP: { mobile: 32, desktop: 48 }, + BTC: { mobile: 45, desktop: 90 }, + USDC: { mobile: 45, desktop: 90 }, + ETH: { mobile: 45, desktop: 90 }, + LTC: { mobile: 45, desktop: 90 }, + UST: { mobile: 45, desktop: 90 }, + eUSDT: { mobile: 45, desktop: 90 }, + Demo: { mobile: 45, desktop: 90 }, +}; + +type TProps = { + type: keyof typeof type_to_icon_mapper | Omit; +}; + +const WalletListCardIcon: React.FC = ({ type }) => { + const { is_mobile } = useDevice(); + const Icon = type_to_icon_mapper[type as keyof typeof type_to_icon_mapper]; + const size = type_to_size_mapper[type as keyof typeof type_to_icon_mapper][is_mobile ? 'mobile' : 'desktop']; + + if (!Icon) return null; + + return ; }; export default WalletListCardIcon; diff --git a/packages/wallets/src/components/WalletsAccordion/WalletsAccordion.scss b/packages/wallets/src/components/WalletsAccordion/WalletsAccordion.scss new file mode 100644 index 000000000000..a5c1408f5458 --- /dev/null +++ b/packages/wallets/src/components/WalletsAccordion/WalletsAccordion.scss @@ -0,0 +1,87 @@ +.wallets-accordion { + width: 100%; + background: var(--system-light-8-primary-background, #fff); + border-radius: 16px; + position: relative; + + &--virtual { + background: var(--system-light-8-primary-background, #fffafa); + } + + &__header { + width: 100%; + height: 100%; + display: flex; + align-items: center; + padding: 24px; + gap: 24px; + border-radius: 16px; + position: relative; + + &--virtual { + &:before { + content: ''; + display: block; + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + border-radius: 16px; + background-image: url('../../public//images/wallet-demo-desktop-bg.svg'); + background-repeat: repeat; + background-size: 60px; + opacity: 0.24; + } + } + } + + &__content { + animation: hide-accordion-content 0.3s ease; + transform: translateY(-3rem); + opacity: 0; + display: none; + + &--visible { + animation: show-accordion-content 0.3s ease; + transform: translateY(0); + opacity: 1; + display: block; + padding-inline: 24px; + padding-bottom: 24px; + } + } + + &__dropdown { + transition: all 0.3s ease; + cursor: pointer; + z-index: 10; + + &--open { + transform: rotate(180deg); + transition: all 0.3s ease; + } + } + + @keyframes show-accordion-content { + from { + transform: translateY(-3rem); + opacity: 0; + } + to { + transform: translateY(0); + opacity: 1; + } + } + + @keyframes hide-accordion-content { + from { + transform: translateY(0); + opacity: 1; + } + to { + transform: translateY(-3rem); + opacity: 0; + } + } +} diff --git a/packages/wallets/src/components/WalletsAccordion/WalletsAccordion.tsx b/packages/wallets/src/components/WalletsAccordion/WalletsAccordion.tsx new file mode 100644 index 000000000000..6c376633fb06 --- /dev/null +++ b/packages/wallets/src/components/WalletsAccordion/WalletsAccordion.tsx @@ -0,0 +1,37 @@ +import React, { ReactElement, useMemo } from 'react'; +import { useWalletAccountsList } from '@deriv/api'; +import IcDropdown from '../../public/images/ic-dropdown.svg'; +import './WalletsAccordion.scss'; + +type TProps = { + account_info?: ReturnType['data'][number]; + active_account?: string; + content: ReactElement; + header: ReactElement; + switchAccount: (loginid?: string) => void; +}; + +const WalletsAccordion: React.FC = ({ active_account, account_info, switchAccount, header, content }) => { + const is_open = useMemo(() => active_account === account_info?.loginid, [active_account]); + + return ( +
+
+ {header} +
switchAccount(account_info?.loginid)} + > + +
+
+
{is_open && content}
+
+ ); +}; + +export default WalletsAccordion; diff --git a/packages/wallets/src/components/WalletsAccordion/index.ts b/packages/wallets/src/components/WalletsAccordion/index.ts new file mode 100644 index 000000000000..055b1ba67c6b --- /dev/null +++ b/packages/wallets/src/components/WalletsAccordion/index.ts @@ -0,0 +1 @@ +export { default as WalletsAccordion } from './WalletsAccordion'; diff --git a/packages/wallets/src/components/WalletsAccordionContainer/WalletsAccordionContainer.scss b/packages/wallets/src/components/WalletsAccordionContainer/WalletsAccordionContainer.scss new file mode 100644 index 000000000000..af954c52fb14 --- /dev/null +++ b/packages/wallets/src/components/WalletsAccordionContainer/WalletsAccordionContainer.scss @@ -0,0 +1,7 @@ +.wallets-accordion-container { + width: 100%; + display: flex; + flex-direction: column; + align-items: center; + gap: 24px; +} diff --git a/packages/wallets/src/components/WalletsAccordionContainer/WalletsAccordionContainer.tsx b/packages/wallets/src/components/WalletsAccordionContainer/WalletsAccordionContainer.tsx new file mode 100644 index 000000000000..d5ed95737927 --- /dev/null +++ b/packages/wallets/src/components/WalletsAccordionContainer/WalletsAccordionContainer.tsx @@ -0,0 +1,38 @@ +import React, { useState } from 'react'; +import { useWalletAccountsList } from '@deriv/api'; +import { AccountsList, WalletListCard, WalletsAccordion } from '..'; +import './WalletsAccordionContainer.scss'; + +type TProps = { + wallets_list: ReturnType['data']; +}; + +const WalletsAccordionContainer: React.FC = ({ wallets_list }) => { + const [active_account, setActiveAccount] = useState(wallets_list.find(account => account.is_active)?.loginid); + + const swithAccount = (loginid?: string) => { + if (!loginid) return; + + setActiveAccount(loginid); + // implement switch account here + }; + + return ( +
+ {wallets_list.map(account => { + return ( + } + content={} + /> + ); + })} +
+ ); +}; + +export default WalletsAccordionContainer; diff --git a/packages/wallets/src/components/WalletsAccordionContainer/index.ts b/packages/wallets/src/components/WalletsAccordionContainer/index.ts new file mode 100644 index 000000000000..c328cdd59ce5 --- /dev/null +++ b/packages/wallets/src/components/WalletsAccordionContainer/index.ts @@ -0,0 +1 @@ +export { default as WalletsAccordionContainer } from './WalletsAccordionContainer'; diff --git a/packages/wallets/src/components/WalletsAddMoreCard/WalletsAddMoreCard.scss b/packages/wallets/src/components/WalletsAddMoreCard/WalletsAddMoreCard.scss new file mode 100644 index 000000000000..dae5d4c70704 --- /dev/null +++ b/packages/wallets/src/components/WalletsAddMoreCard/WalletsAddMoreCard.scss @@ -0,0 +1,20 @@ +.wallets-add-more { + &__card { + display: flex; + flex-direction: column; + gap: 2.4rem; + padding: 1.6rem; + width: 23.2rem; + height: 28.2rem; + border-radius: 1.6rem; + flex-shrink: 0; + border: 0.1rem solid var(--system-light-5-active-background, #d6dadb); + background: var(--system-light-8-primary-background, #fff); + box-shadow: 0px 4px 6px -2px rgba(14, 14, 14, 0.03), 0px 12px 16px -4px rgba(14, 14, 14, 0.08); + + @include mobile { + width: 19.2rem; + gap: 1.6rem; + } + } +} diff --git a/packages/wallets/src/components/WalletsAddMoreCard/WalletsAddMoreCard.tsx b/packages/wallets/src/components/WalletsAddMoreCard/WalletsAddMoreCard.tsx new file mode 100644 index 000000000000..092f3935f8b3 --- /dev/null +++ b/packages/wallets/src/components/WalletsAddMoreCard/WalletsAddMoreCard.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import { useAvailableWallets } from '@deriv/api'; +import { WalletGradientBackground } from '../WalletGradientBackground'; +import WalletsAddMoreCardBanner from '../WalletsAddMoreCardBanner'; +import WalletsAddMoreCardContent from '../WalletsAddMoreCardContent'; + +type TWalletsAddMoreCard = NonNullable['data']>[0]; + +const WalletsAddMoreCard = ({ currency, is_added, landing_company_name }: TWalletsAddMoreCard) => { + return ( +
+ + + + +
+ ); +}; + +export default WalletsAddMoreCard; diff --git a/packages/wallets/src/components/WalletsAddMoreCard/index.ts b/packages/wallets/src/components/WalletsAddMoreCard/index.ts new file mode 100644 index 000000000000..329f989507ce --- /dev/null +++ b/packages/wallets/src/components/WalletsAddMoreCard/index.ts @@ -0,0 +1,4 @@ +import WalletsAddMoreCard from './WalletsAddMoreCard'; +import './WalletsAddMoreCard.scss'; + +export default WalletsAddMoreCard; diff --git a/packages/wallets/src/components/WalletsAddMoreCardBanner/WalletsAddMoreCardBanner.scss b/packages/wallets/src/components/WalletsAddMoreCardBanner/WalletsAddMoreCardBanner.scss new file mode 100644 index 000000000000..687b62fff39f --- /dev/null +++ b/packages/wallets/src/components/WalletsAddMoreCardBanner/WalletsAddMoreCardBanner.scss @@ -0,0 +1,49 @@ +.wallets-add-more { + &__banner { + border-radius: 0.8rem; + display: flex; + flex-direction: column; + justify-content: space-between; + width: 20rem; + height: 12rem; + padding: 1.6rem; + background: none; + + @include mobile { + width: 16rem; + height: 9.6rem; + padding: 0.8rem; + flex-shrink: 0; + } + &-header { + display: flex; + justify-content: space-between; + } + &-button { + cursor: pointer; + width: 100%; + padding: 0.6rem 1.6rem; + font-weight: 700; + + @include mobile { + padding: 0.3rem 0.8rem; + } + &--is-added { + opacity: 0.3; + cursor: default; + } + } + &-landing-company { + border: 0.1rem solid #000; + border-radius: 0.2rem; + padding: 0 0.4rem; + font-weight: 700; + line-height: 1.4rem; + + @include mobile { + line-height: 1.2rem; + font-size: 0.8rem; + } + } + } +} diff --git a/packages/wallets/src/components/WalletsAddMoreCardBanner/WalletsAddMoreCardBanner.tsx b/packages/wallets/src/components/WalletsAddMoreCardBanner/WalletsAddMoreCardBanner.tsx new file mode 100644 index 000000000000..f2e1451d032a --- /dev/null +++ b/packages/wallets/src/components/WalletsAddMoreCardBanner/WalletsAddMoreCardBanner.tsx @@ -0,0 +1,27 @@ +import React from 'react'; + +const WalletsAddMoreCardBanner = ({ + is_added, + landing_company_name, +}: { + is_added: boolean; + landing_company_name: string; +}) => { + return ( +
+
+ LOGO + {landing_company_name.toUpperCase()} +
+ +
+ ); +}; + +export default WalletsAddMoreCardBanner; diff --git a/packages/wallets/src/components/WalletsAddMoreCardBanner/index.ts b/packages/wallets/src/components/WalletsAddMoreCardBanner/index.ts new file mode 100644 index 000000000000..be1c2c448a94 --- /dev/null +++ b/packages/wallets/src/components/WalletsAddMoreCardBanner/index.ts @@ -0,0 +1,4 @@ +import WalletsAddMoreCardBanner from './WalletsAddMoreCardBanner'; +import './WalletsAddMoreCardBanner.scss'; + +export default WalletsAddMoreCardBanner; diff --git a/packages/wallets/src/components/WalletsAddMoreCardContent/WalletsAddMoreCardContent.scss b/packages/wallets/src/components/WalletsAddMoreCardContent/WalletsAddMoreCardContent.scss new file mode 100644 index 000000000000..ba2ce43d03b7 --- /dev/null +++ b/packages/wallets/src/components/WalletsAddMoreCardContent/WalletsAddMoreCardContent.scss @@ -0,0 +1,23 @@ +.wallets-add-more { + &__content { + display: flex; + flex-direction: column; + gap: 0.8rem; + + &-header { + font-weight: 700; + line-height: 2.4rem; + font-size: 1.6rem; + + @include mobile { + font-size: 1.4rem; + line-height: 2rem; + } + } + &-body { + font-weight: 400; + line-height: 2rem; + font-size: 1.4rem; + } + } +} diff --git a/packages/wallets/src/components/WalletsAddMoreCardContent/WalletsAddMoreCardContent.tsx b/packages/wallets/src/components/WalletsAddMoreCardContent/WalletsAddMoreCardContent.tsx new file mode 100644 index 000000000000..668880c80472 --- /dev/null +++ b/packages/wallets/src/components/WalletsAddMoreCardContent/WalletsAddMoreCardContent.tsx @@ -0,0 +1,30 @@ +import React from 'react'; + +type TWalletDescriptionMapper = { + [key: string]: string; +}; + +const wallet_description_mapper: TWalletDescriptionMapper = { + AUD: 'Deposit and withdraw Australian dollars using credit or debit cards, e-wallets, or bank wires.', + EUR: 'Deposit and withdraw euros into your accounts regulated by MFSA using credit or debit cards and e-wallets.', + USD: 'Deposit and withdraw US dollars using credit or debit cards, e-wallets, or bank wires.', + BTC: "Deposit and withdraw Bitcoin, the world's most popular cryptocurrency, hosted on the Bitcoin blockchain.", + ETH: 'Deposit and withdraw Ether, the fastest growing cryptocurrency, hosted on the Ethereum blockchain.', + LTC: 'Deposit and withdraw Litecoin, the cryptocurrency with low transaction fees, hosted on the Litecoin blockchain.', + USDC: 'Deposit and withdraw USD Coin, hosted on the Ethereum blockchain.', + eUSDT: 'Deposit and withdraw Tether ERC20, a version of Tether hosted on the Ethereum blockchain.', + tUSDT: 'Deposit and withdraw Tether TRC20, a version of Tether hosted on the TRON blockchain.', + UST: 'Deposit and withdraw Tether Omni, hosted on the Bitcoin blockchain.', + PaymentAgent: 'Deposit and withdraw funds via authorised, independent payment agents.', +}; + +const WalletsAddMoreCardContent = ({ currency }: { currency: string }) => { + return ( +
+

{currency} Wallet

+
{wallet_description_mapper[currency]}
+
+ ); +}; + +export default WalletsAddMoreCardContent; diff --git a/packages/wallets/src/components/WalletsAddMoreCardContent/index.ts b/packages/wallets/src/components/WalletsAddMoreCardContent/index.ts new file mode 100644 index 000000000000..fcadffd20717 --- /dev/null +++ b/packages/wallets/src/components/WalletsAddMoreCardContent/index.ts @@ -0,0 +1,4 @@ +import WalletsAddMoreCardContent from './WalletsAddMoreCardContent'; +import './WalletsAddMoreCardContent.scss'; + +export default WalletsAddMoreCardContent; diff --git a/packages/wallets/src/components/WalletsAddMoreCarousel/WalletsAddMoreCarousel.scss b/packages/wallets/src/components/WalletsAddMoreCarousel/WalletsAddMoreCarousel.scss new file mode 100644 index 000000000000..16ddf1c5fe76 --- /dev/null +++ b/packages/wallets/src/components/WalletsAddMoreCarousel/WalletsAddMoreCarousel.scss @@ -0,0 +1,70 @@ +.wallets-add-more { + display: flex; + flex-direction: column; + width: 100%; + padding-top: 2.4rem; + + @include mobile { + padding-top: 0; + gap: 0.8rem; + } + + &__header { + font-size: 3.2rem; + font-weight: 700; + margin-bottom: 1.6rem; + + @include mobile { + font-size: 2.8rem; + padding-left: 1.6rem; + margin-bottom: 0; + } + } + &__carousel { + overflow: hidden; + background-color: #fff; + padding: 3.2rem 0 3.2rem 1.6rem; + border-radius: 0.8rem; + position: relative; + border: 0.1rem solid #d6dadb; + height: 100%; + + @include mobile { + padding: 1.6rem 0 2.4rem 1.6rem; + border-radius: unset; + } + + &-wrapper { + height: 100%; + display: flex; + align-items: center; + justify-content: flex-start; + gap: 2.4rem; + + @include mobile { + gap: 1.6rem; + } + } + + &-btn { + z-index: 1; + position: absolute; + height: 4rem; + width: 4rem; + border-radius: 50%; + cursor: pointer; + top: 45%; + + &--prev { + left: 1.6rem; + } + &--next { + right: 1.6rem; + } + &:disabled { + opacity: 0.3; + display: none; + } + } + } +} diff --git a/packages/wallets/src/components/WalletsAddMoreCarousel/WalletsAddMoreCarousel.tsx b/packages/wallets/src/components/WalletsAddMoreCarousel/WalletsAddMoreCarousel.tsx new file mode 100644 index 000000000000..e8c971816633 --- /dev/null +++ b/packages/wallets/src/components/WalletsAddMoreCarousel/WalletsAddMoreCarousel.tsx @@ -0,0 +1,86 @@ +import React, { useCallback, useEffect, useState } from 'react'; +import useEmblaCarousel, { EmblaCarouselType, EmblaOptionsType } from 'embla-carousel-react'; +import { useAvailableWallets } from '@deriv/api'; +import useDevice from '../../hooks/useDevice'; +import WalletsAddMoreCard from '../WalletsAddMoreCard'; + +const WalletsAddMoreCarousel = () => { + const { is_mobile } = useDevice(); + const { data: available_wallets } = useAvailableWallets(); + const options: EmblaOptionsType = { + align: 0, + containScroll: 'trimSnaps', + }; + + const [WalletsAddMoreRef, emblaApi] = useEmblaCarousel(options); + const [is_hovered, setIsHovered] = useState(is_mobile); + const [prev_btn_enabled, setPrevBtnEnabled] = useState(false); + const [next_btn_enabled, setNextBtnEnabled] = useState(false); + + const scrollPrev = useCallback(() => emblaApi?.scrollPrev(), [emblaApi]); + const scrollNext = useCallback(() => emblaApi?.scrollNext(), [emblaApi]); + + useEffect(() => { + if (!emblaApi) return; + + const onSelect = (embla_api: EmblaCarouselType) => { + setPrevBtnEnabled(embla_api.canScrollPrev()); + setNextBtnEnabled(embla_api.canScrollNext()); + }; + + onSelect(emblaApi); + emblaApi.on('reInit', onSelect); + emblaApi.reInit({ watchDrag: is_mobile }); + emblaApi.on('select', onSelect); + }, [emblaApi, is_mobile]); + + if (!available_wallets?.length) return

No wallets found

; + return ( + +
+

Add more Wallets

+
!is_mobile && setIsHovered(true)} + onMouseLeave={() => !is_mobile && setIsHovered(false)} + > +
+ {available_wallets?.map(item => { + const { currency, is_added, landing_company_name } = item; + return ( + + ); + })} +
+ {!is_mobile && is_hovered && ( + + + + + )} +
+
+
+ ); +}; + +export default WalletsAddMoreCarousel; diff --git a/packages/wallets/src/components/WalletsAddMoreCarousel/index.ts b/packages/wallets/src/components/WalletsAddMoreCarousel/index.ts new file mode 100644 index 000000000000..8bc14603240d --- /dev/null +++ b/packages/wallets/src/components/WalletsAddMoreCarousel/index.ts @@ -0,0 +1,4 @@ +import WalletsAddMoreCarousel from './WalletsAddMoreCarousel'; +import './WalletsAddMoreCarousel.scss'; + +export default WalletsAddMoreCarousel; diff --git a/packages/wallets/src/components/WalletsCarousel/WalletsCarousel.scss b/packages/wallets/src/components/WalletsCarousel/WalletsCarousel.scss index 8efe2e66b088..e861eb02afab 100644 --- a/packages/wallets/src/components/WalletsCarousel/WalletsCarousel.scss +++ b/packages/wallets/src/components/WalletsCarousel/WalletsCarousel.scss @@ -1,53 +1,7 @@ .wallets-carousel { - background-color: #f2f3f4; - width: 100vw; - padding: 2rem; - overflow: hidden; - - &__container { + @include mobile { height: 100%; - display: flex; - align-items: center; - justify-content: flex-start; - gap: 2.4rem; - } -} - -.wallets-card { - width: 100%; - height: 13rem; - background-color: #add8e6; - - &__data { - display: flex; - justify-content: space-between; - width: 100%; - height: 100%; - padding: 1.6rem; - - &__details { - display: flex; - flex-direction: column; - justify-content: space-between; - - &-balance { - display: flex; - flex-direction: column; - gap: 1rem; - - & > h3 { - font-size: 1.6rem; - font-weight: bold; - } - } - } - - &__landing-company { - font-size: 1rem; - font-weight: bold; - padding: 0.5rem; - height: max-content; - border: 1px solid #000000; - } + overflow-x: hidden; + overflow-y: auto; } } diff --git a/packages/wallets/src/components/WalletsCarousel/WalletsCarousel.tsx b/packages/wallets/src/components/WalletsCarousel/WalletsCarousel.tsx index d8f423adc2c8..30d144ca38e5 100644 --- a/packages/wallets/src/components/WalletsCarousel/WalletsCarousel.tsx +++ b/packages/wallets/src/components/WalletsCarousel/WalletsCarousel.tsx @@ -1,52 +1,14 @@ -import React, { useState } from 'react'; -import useEmblaCarousel from 'embla-carousel-react'; -import { useWalletAccountsList } from '@deriv/api'; -import AccountsList from '../AccountsList'; - -const WalletsCarousel = () => { - const [emblaRef, emblaApi] = useEmblaCarousel({ skipSnaps: true, containScroll: false }); - const [active_index, setActiveIndex] = useState(0); - const { data: wallet_accounts_list } = useWalletAccountsList(); - - React.useEffect(() => { - emblaApi?.scrollTo(active_index); - }, [active_index, emblaApi]); - - React.useEffect(() => { - emblaApi?.on('select', () => { - const scroll_snap_index = emblaApi.selectedScrollSnap(); - setActiveIndex(scroll_snap_index); - }); - }, [emblaApi]); - - if (!wallet_accounts_list.length) return

No wallets found

; +import React from 'react'; +import { AccountsList } from '../AccountsList'; +import { WalletsCarouselContent } from '../WalletsCarouselContent'; +import './WalletsCarousel.scss'; +const WalletsCarousel: React.FC = () => { return ( - -
-
- {wallet_accounts_list.map(wallet => ( -
-
-
-

{wallet.currency}

-
-

{wallet.currency} Wallet

-

- {wallet.balance} {wallet.currency} -

-
-
-
-

{wallet.landing_company_name}

-
-
-
- ))} -
-
- -
+
+ + +
); }; diff --git a/packages/wallets/src/components/WalletsCarousel/index.ts b/packages/wallets/src/components/WalletsCarousel/index.ts index c4cd069815b0..e25162cca9c7 100644 --- a/packages/wallets/src/components/WalletsCarousel/index.ts +++ b/packages/wallets/src/components/WalletsCarousel/index.ts @@ -1,4 +1 @@ -import WalletsCarousel from './WalletsCarousel'; -import './WalletsCarousel.scss'; - -export default WalletsCarousel; +export { default as WalletsCarousel } from './WalletsCarousel'; diff --git a/packages/wallets/src/components/WalletsCarouselContent/WalletsCarouselContent.scss b/packages/wallets/src/components/WalletsCarouselContent/WalletsCarouselContent.scss new file mode 100644 index 000000000000..ec93ed351bb5 --- /dev/null +++ b/packages/wallets/src/components/WalletsCarouselContent/WalletsCarouselContent.scss @@ -0,0 +1,22 @@ +.wallets-carousel-content { + background-color: #f2f3f4; + width: 100vw; + padding: 2rem; + overflow: hidden; + gap: 1.6rem; + + &__container { + height: 100%; + display: flex; + align-items: center; + justify-content: flex-start; + gap: 2.4rem; + } + + &__progress-bar { + display: flex; + align-items: center; + justify-content: center; + padding: 1.6rem; + } +} diff --git a/packages/wallets/src/components/WalletsCarouselContent/WalletsCarouselContent.tsx b/packages/wallets/src/components/WalletsCarouselContent/WalletsCarouselContent.tsx new file mode 100644 index 000000000000..5dd540d0a9df --- /dev/null +++ b/packages/wallets/src/components/WalletsCarouselContent/WalletsCarouselContent.tsx @@ -0,0 +1,46 @@ +import React, { useState } from 'react'; +import useEmblaCarousel from 'embla-carousel-react'; +import { useWalletAccountsList } from '@deriv/api'; +import { ProgressBar } from '../ProgressBar'; +import { WalletCard } from '../WalletCard'; +import { WalletListCardIActions } from '../WalletListCardIActions'; +import './WalletsCarouselContent.scss'; + +const WalletsCarouselContent: React.FC = () => { + const [wallet_embla_ref, emblaApi] = useEmblaCarousel({ skipSnaps: true, containScroll: false }); + const [active_index, setActiveIndex] = useState(0); + const { data: wallet_accounts_list } = useWalletAccountsList(); + + React.useEffect(() => { + emblaApi?.scrollTo(active_index); + }, [active_index, emblaApi]); + + React.useEffect(() => { + emblaApi?.on('select', () => { + const scroll_snap_index = emblaApi.selectedScrollSnap(); + setActiveIndex(scroll_snap_index); + }); + }, [emblaApi]); + const amount_of_steps = Array.from({ length: wallet_accounts_list.length }, (_, idx) => idx + 1); + + return ( +
+
+ {wallet_accounts_list.map(wallet => ( + + ))} +
+
+ +
+ +
+ ); +}; + +export default WalletsCarouselContent; diff --git a/packages/wallets/src/components/WalletsCarouselContent/index.ts b/packages/wallets/src/components/WalletsCarouselContent/index.ts new file mode 100644 index 000000000000..5dd167fd80db --- /dev/null +++ b/packages/wallets/src/components/WalletsCarouselContent/index.ts @@ -0,0 +1 @@ +export { default as WalletsCarouselContent } from './WalletsCarouselContent'; diff --git a/packages/wallets/src/components/index.ts b/packages/wallets/src/components/index.ts index bd6784a158c3..a224770a9fb0 100644 --- a/packages/wallets/src/components/index.ts +++ b/packages/wallets/src/components/index.ts @@ -1,10 +1,23 @@ +export * from './AccountsList'; +export * from './CFDList'; +export * from './CTraderList'; export * from './DesktopWalletsList'; -export * from './WalletListCardIBalance'; -export * from './WalletListCardIcon'; -export * from './WalletListCardTitle'; +export * from './MT5List'; +export * from './OptionsAndMultipliersListing'; +export * from './TradingAccountCard'; +export * from './WalletListCard'; export * from './WalletListCardBadge'; export * from './WalletListCardIActions'; -export * from './WalletListCard'; +export * from './WalletListCardIBalance'; export * from './WalletListCardIDetails'; -export * from './AccountsList'; +export * from './WalletListCardIcon'; +export * from './WalletListCardTitle'; +export * from './WalletsAccordion'; +export * from './WalletsAccordionContainer'; export * from './WalletsCarousel'; +export * from './WalletCard'; +export * from './ProgressBar'; +export * from './WalletsCarouselContent'; +export * from './OtherCFDPlatformsList'; +export * from './SecondaryActionButton'; +export * from './PrimaryActionButton'; diff --git a/packages/wallets/src/hooks/useDevice.tsx b/packages/wallets/src/hooks/useDevice.tsx new file mode 100644 index 000000000000..e4d00355d061 --- /dev/null +++ b/packages/wallets/src/hooks/useDevice.tsx @@ -0,0 +1,17 @@ +import { useWindowSize } from 'usehooks-ts'; + +/** A custom hook to check for the client device and determine the layout to be rendered */ +const useDevice = () => { + const { width } = useWindowSize(); + const is_mobile = width < 768; + const is_tablet = width >= 768 && width < 1024; + const is_desktop = width >= 1024; + + return { + is_mobile, + is_tablet, + is_desktop, + }; +}; + +export default useDevice; diff --git a/packages/wallets/src/index.scss b/packages/wallets/src/index.scss new file mode 100644 index 000000000000..44dfbcbe46dd --- /dev/null +++ b/packages/wallets/src/index.scss @@ -0,0 +1 @@ +@import './styles/devices.scss'; diff --git a/packages/wallets/src/index.tsx b/packages/wallets/src/index.tsx index 09425f97f69b..d8aa16d9aa96 100644 --- a/packages/wallets/src/index.tsx +++ b/packages/wallets/src/index.tsx @@ -1,10 +1,11 @@ import React from 'react'; import { APIProvider } from '@deriv/api'; import AppContent from './AppContent'; +import './index.scss'; const App: React.FC = () => ( - ; + ); diff --git a/packages/wallets/src/public/images/bitcion.svg b/packages/wallets/src/public/images/bitcion.svg new file mode 100644 index 000000000000..e1f0a9e7b7b9 --- /dev/null +++ b/packages/wallets/src/public/images/bitcion.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/wallets/src/public/images/ctrader.svg b/packages/wallets/src/public/images/ctrader.svg new file mode 100644 index 000000000000..c021be146cd8 --- /dev/null +++ b/packages/wallets/src/public/images/ctrader.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/wallets/src/public/images/demo.svg b/packages/wallets/src/public/images/demo.svg new file mode 100644 index 000000000000..08692518f65d --- /dev/null +++ b/packages/wallets/src/public/images/demo.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/packages/wallets/src/public/images/derivez.svg b/packages/wallets/src/public/images/derivez.svg new file mode 100644 index 000000000000..9de4d430385a --- /dev/null +++ b/packages/wallets/src/public/images/derivez.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/wallets/src/public/images/derivx.svg b/packages/wallets/src/public/images/derivx.svg new file mode 100644 index 000000000000..7d000319b6cf --- /dev/null +++ b/packages/wallets/src/public/images/derivx.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/wallets/src/public/images/eth.svg b/packages/wallets/src/public/images/eth.svg new file mode 100644 index 000000000000..2d44b727e9d8 --- /dev/null +++ b/packages/wallets/src/public/images/eth.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/packages/wallets/src/public/images/eur.svg b/packages/wallets/src/public/images/eur.svg new file mode 100644 index 000000000000..18edd9c10437 --- /dev/null +++ b/packages/wallets/src/public/images/eur.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packages/wallets/src/public/images/eusdt.svg b/packages/wallets/src/public/images/eusdt.svg new file mode 100644 index 000000000000..f175521b3da5 --- /dev/null +++ b/packages/wallets/src/public/images/eusdt.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/packages/wallets/src/public/images/gbp.svg b/packages/wallets/src/public/images/gbp.svg new file mode 100644 index 000000000000..9f6e81c78097 --- /dev/null +++ b/packages/wallets/src/public/images/gbp.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/wallets/src/public/images/ic-appstore-binary-bot.svg b/packages/wallets/src/public/images/ic-appstore-binary-bot.svg new file mode 100644 index 000000000000..cc30b74c7f4b --- /dev/null +++ b/packages/wallets/src/public/images/ic-appstore-binary-bot.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/wallets/src/public/images/ic-appstore-deriv-bot.svg b/packages/wallets/src/public/images/ic-appstore-deriv-bot.svg new file mode 100644 index 000000000000..3ac9c2c0aaf6 --- /dev/null +++ b/packages/wallets/src/public/images/ic-appstore-deriv-bot.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/wallets/src/public/images/ic-appstore-deriv-go.svg b/packages/wallets/src/public/images/ic-appstore-deriv-go.svg new file mode 100644 index 000000000000..84c6020b8183 --- /dev/null +++ b/packages/wallets/src/public/images/ic-appstore-deriv-go.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/wallets/src/public/images/ic-appstore-deriv-trader.svg b/packages/wallets/src/public/images/ic-appstore-deriv-trader.svg new file mode 100644 index 000000000000..09cfff46d877 --- /dev/null +++ b/packages/wallets/src/public/images/ic-appstore-deriv-trader.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/wallets/src/public/images/ic-appstore-smart-trader.svg b/packages/wallets/src/public/images/ic-appstore-smart-trader.svg new file mode 100644 index 000000000000..eabd99eaa29e --- /dev/null +++ b/packages/wallets/src/public/images/ic-appstore-smart-trader.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/wallets/src/public/images/ltc.svg b/packages/wallets/src/public/images/ltc.svg new file mode 100644 index 000000000000..97a0d887553b --- /dev/null +++ b/packages/wallets/src/public/images/ltc.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/packages/wallets/src/public/images/mt5-derived.svg b/packages/wallets/src/public/images/mt5-derived.svg new file mode 100644 index 000000000000..facf568c1bc0 --- /dev/null +++ b/packages/wallets/src/public/images/mt5-derived.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/wallets/src/public/images/mt5-financial.svg b/packages/wallets/src/public/images/mt5-financial.svg new file mode 100644 index 000000000000..e920dc117d63 --- /dev/null +++ b/packages/wallets/src/public/images/mt5-financial.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/wallets/src/public/images/mt5-swap-free.svg b/packages/wallets/src/public/images/mt5-swap-free.svg new file mode 100644 index 000000000000..3eadc1c209a8 --- /dev/null +++ b/packages/wallets/src/public/images/mt5-swap-free.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/wallets/src/public/images/usd.svg b/packages/wallets/src/public/images/usd.svg new file mode 100644 index 000000000000..7e47b5363b5a --- /dev/null +++ b/packages/wallets/src/public/images/usd.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/wallets/src/public/images/usdc.svg b/packages/wallets/src/public/images/usdc.svg new file mode 100644 index 000000000000..625dee67fafb --- /dev/null +++ b/packages/wallets/src/public/images/usdc.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/packages/wallets/src/public/images/wallet-demo-bg-dark.svg b/packages/wallets/src/public/images/wallet-demo-bg-dark.svg new file mode 100644 index 000000000000..6d1bf48f4626 --- /dev/null +++ b/packages/wallets/src/public/images/wallet-demo-bg-dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/wallets/src/public/images/wallet-demo-bg-light.svg b/packages/wallets/src/public/images/wallet-demo-bg-light.svg new file mode 100644 index 000000000000..12660fa7e760 --- /dev/null +++ b/packages/wallets/src/public/images/wallet-demo-bg-light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/wallets/src/public/images/wallet-demo-desktop-bg.svg b/packages/wallets/src/public/images/wallet-demo-desktop-bg.svg new file mode 100644 index 000000000000..7626c01c3d73 --- /dev/null +++ b/packages/wallets/src/public/images/wallet-demo-desktop-bg.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/packages/wallets/src/styles/devices.scss b/packages/wallets/src/styles/devices.scss new file mode 100644 index 000000000000..4d4ae2ddf80d --- /dev/null +++ b/packages/wallets/src/styles/devices.scss @@ -0,0 +1,23 @@ +/** + * Define Breakpoints here. + */ +$tablet-width: 768px; +$desktop-width: 1024px; + +@mixin mobile { + @media (max-width: #{$tablet-width - 1px}) { + @content; + } +} + +@mixin tablet { + @media (min-width: #{$tablet-width}) and (max-width: #{$desktop-width - 1px}) { + @content; + } +} + +@mixin desktop { + @media (min-width: #{$desktop-width}) { + @content; + } +} diff --git a/packages/wallets/src/styles/index.js b/packages/wallets/src/styles/index.js new file mode 100644 index 000000000000..2dfca1c0d736 --- /dev/null +++ b/packages/wallets/src/styles/index.js @@ -0,0 +1,5 @@ +const path = require('path'); + +const resources = ['devices.scss']; + +module.exports = resources.map(file => path.resolve(__dirname, file)); diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 000000000000..fb49cc494a29 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,54 @@ +import { defineConfig, devices } from '@playwright/test'; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// require('dotenv').config(); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './integration-tests', + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + baseURL: 'https://localhost.binary.sx', + ignoreHTTPSErrors: true, + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + }, + + /* Run tests for each of our packages from root of the project */ + { + name: 'account', + testDir: './packages/account/integration-tests', + }, + { + name: 'integration', + testDir: './packages/integration/integration-tests', + }, + ], +}); diff --git a/types/global.d.ts b/types/global.d.ts index 9ce39f69070e..2f5bbbfa742c 100644 --- a/types/global.d.ts +++ b/types/global.d.ts @@ -1,5 +1,6 @@ declare global { interface Window { + clipboardData: DataTransfer; LiveChatWidget: { init: () => void; on: (key: string, callback: VoidFunction) => void; diff --git a/types/utils.d.ts b/types/utils.d.ts index 20e842a31490..4362e2eba296 100644 --- a/types/utils.d.ts +++ b/types/utils.d.ts @@ -3,6 +3,10 @@ declare global { [K in keyof T]-?: T[K] extends V ? K : never; }[keyof T]; + type ObjectEntries = { + [K in keyof T]: [K, T[K]]; + }[keyof T][]; + type DeepPartial = T extends string | number | bigint | boolean | null | undefined | symbol | Date ? T | undefined : T extends Array