diff --git a/.github/workflows/ci-cd-manual.yml b/.github/workflows/ci-cd-manual.yml index fa018333a..30df949b3 100644 --- a/.github/workflows/ci-cd-manual.yml +++ b/.github/workflows/ci-cd-manual.yml @@ -28,13 +28,13 @@ jobs: strategy: matrix: - node-version: [16.x] + node-version: [18.x] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} diff --git a/.github/workflows/ci-check.yml b/.github/workflows/ci-check.yml index f6b31ce92..eb59f5a41 100644 --- a/.github/workflows/ci-check.yml +++ b/.github/workflows/ci-check.yml @@ -12,14 +12,14 @@ jobs: strategy: matrix: - node-version: [16.x] + node-version: [18.x] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Cache npm dependencies id: cache-npm - uses: actions/cache@v3 + uses: actions/cache@v4 env: cache-name: cache-node-modules with: @@ -32,7 +32,7 @@ jobs: ${{ runner.os }}- - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} diff --git a/.github/workflows/cleanup-caches.yml b/.github/workflows/cleanup-caches.yml index 66a50b336..b6a753d2d 100644 --- a/.github/workflows/cleanup-caches.yml +++ b/.github/workflows/cleanup-caches.yml @@ -17,7 +17,7 @@ jobs: steps: - name: Check out code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Cleanup run: | diff --git a/.github/workflows/e2e-onboarding-tests.yml b/.github/workflows/e2e-onboarding-tests.yml index f81a8a7b2..beb816132 100644 --- a/.github/workflows/e2e-onboarding-tests.yml +++ b/.github/workflows/e2e-onboarding-tests.yml @@ -14,8 +14,8 @@ jobs: image: mcr.microsoft.com/playwright:v1.39.0-jammy steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 with: node-version: 18 @@ -28,14 +28,14 @@ jobs: - name: Run Chrome tests run: npm run e2e:chrome:headless:onboarding - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 if: failure() with: name: playwright-report path: playwright-report/ retention-days: 30 - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 if: failure() with: name: test-results diff --git a/.github/workflows/e2e-popup-tests.yml b/.github/workflows/e2e-popup-tests.yml index dc55998c7..b6174c6f2 100644 --- a/.github/workflows/e2e-popup-tests.yml +++ b/.github/workflows/e2e-popup-tests.yml @@ -14,8 +14,8 @@ jobs: image: mcr.microsoft.com/playwright:v1.39.0-jammy steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 with: node-version: 18 @@ -28,14 +28,14 @@ jobs: - name: Run Chrome tests run: npm run e2e:chrome:headless:popup - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 if: failure() with: name: playwright-report path: playwright-report/ retention-days: 30 - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 if: failure() with: name: test-results diff --git a/.nvmrc b/.nvmrc index 9e15be387..5f09eed8d 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -v16.20.0 +v18.20.3 diff --git a/e2e-tests/constants.ts b/e2e-tests/constants.ts index 04534c06d..05b7d7c91 100644 --- a/e2e-tests/constants.ts +++ b/e2e-tests/constants.ts @@ -70,19 +70,68 @@ export const DEFAULT_SECOND_ACCOUNT = { mediumTruncatedPublicKey: '0203b2e05f...8e91aa7724' }; -export const RECOVER_ACCOUNT_FROM_TWELVE_WORDS = { +export const RECOVER_FIRST_ACCOUNT_FROM_TWELVE_WORDS = { accountName: 'Account 1', publicKey: '0202b869dbed03ef2cc6a76e54e1a5c588fbe6198f80937994f9a2c1fd3aff4adc1b', truncatedPublicKey: '0202b...adc1b' }; -export const VALIDATOR = { +export const RECOVER_SECOND_ACCOUNT_FROM_TWELVE_WORDS = { + accountName: 'Account 1', + publicKey: + '02022cafccfb61ffc4e4221e4d2c38eec6035d579d04c1396b1c2027dc0729c53589', + truncatedPublicKey: '02022...53589' +}; + +export const VALIDATOR_FOR_SIGNATURE_REQUEST = { name: 'Validator', truncatedPublicKey: '0106c...ca2ca' }; -export const NEW_VALIDATOR = { +export const NEW_VALIDATOR_FOR_SIGNATURE_REQUEST = { name: 'New validator', truncatedPublicKey: '017d9...2009e' }; + +export const VALIDATOR_FOR_STAKE = { + publicKey: + '010e5669b070545e2b32bc66363b9d3d4390fca56bf52a05f1411b7fa18ca311c7', + truncatedPublicKey: '010e...11c7' +}; + +export const NEW_VALIDATOR_FOR_STAKE = { + publicKey: + '01ad002e37667f90aa982396ebdfcc7d3eea99731241eaad8a0dc20f453f72975a', + truncatedPublicKey: '01ad...975a' +}; + +export const URLS = { + rpc: 'https://node.testnet.cspr.cloud/rpc' +}; + +export const RPC_RESPONSE = { + success: { + status: 200, + body: JSON.stringify({ + jsonrpc: '2.0', + id: 1717761373590, + result: { + api_version: '1.5.6', + deploy_hash: 'deploy hash' + } + }) + }, + failure: { + status: 500, + body: JSON.stringify({ + jsonrpc: '2.0', + id: 1717761373590, + error: { + code: '', + data: 'Error description', + message: 'Error message' + } + }) + } +}; diff --git a/e2e-tests/onboarding-flow/recover-secret-phrase-flow.spec.ts b/e2e-tests/onboarding-flow/recover-secret-phrase-flow.spec.ts index b3d030e70..45d22d064 100644 --- a/e2e-tests/onboarding-flow/recover-secret-phrase-flow.spec.ts +++ b/e2e-tests/onboarding-flow/recover-secret-phrase-flow.spec.ts @@ -1,6 +1,8 @@ import { DEFAULT_FIRST_ACCOUNT, - RECOVER_ACCOUNT_FROM_TWELVE_WORDS, + DEFAULT_SECOND_ACCOUNT, + RECOVER_FIRST_ACCOUNT_FROM_TWELVE_WORDS, + RECOVER_SECOND_ACCOUNT_FROM_TWELVE_WORDS, twelveWordsSecretPhrase, twentyFourWordsSecretPhrase } from '../constants'; @@ -8,7 +10,7 @@ import { onboarding, onboardingExpect } from '../fixtures'; onboarding.describe('Onboarding UI: recover secret phrase flow', () => { onboarding( - 'should recover account via 24 words secret phrase', + 'should recover one account via 24 words secret phrase', async ({ page, createOnboardingPassword, extensionId }) => { await createOnboardingPassword(); @@ -26,10 +28,27 @@ onboarding.describe('Onboarding UI: recover secret phrase flow', () => { .getByPlaceholder('e.g. Bobcat Lemon Blanket…') .fill(twentyFourWordsSecretPhrase); - await page.getByRole('button', { name: 'Recover my wallet' }).click(); + await page.getByRole('button', { name: 'Next' }).click(); + + await onboardingExpect( + page.getByText('Select accounts to recover') + ).toBeVisible(); + + await onboardingExpect( + page.getByRole('button', { name: 'Recover selected accounts' }) + ).toBeDisabled(); + + await page.getByTestId('select-account-0').click(); + + await page + .getByRole('button', { name: 'Recover selected accounts' }) + .click(); await page.goto(`chrome-extension://${extensionId}/popup.html`); + await page.getByTestId('menu-open-icon').click(); + await page.getByText('All accounts').click(); + await onboardingExpect( page.getByText(DEFAULT_FIRST_ACCOUNT.accountName) ).toBeVisible(); @@ -38,8 +57,64 @@ onboarding.describe('Onboarding UI: recover secret phrase flow', () => { ).toBeVisible(); } ); + onboarding( - 'should recover account via 12 words secret phrase', + 'should recover two accounts via 24 words secret phrase', + async ({ page, createOnboardingPassword, extensionId }) => { + await createOnboardingPassword(); + + await page + .getByRole('button', { + name: 'Import an existing secret recovery phrase' + }) + .click(); + + await onboardingExpect( + page.getByText('Please enter your secret recovery phrase') + ).toBeVisible(); + + await page + .getByPlaceholder('e.g. Bobcat Lemon Blanket…') + .fill(twentyFourWordsSecretPhrase); + + await page.getByRole('button', { name: 'Next' }).click(); + + await onboardingExpect( + page.getByText('Select accounts to recover') + ).toBeVisible(); + + await onboardingExpect( + page.getByRole('button', { name: 'Recover selected accounts' }) + ).toBeDisabled(); + + await page.getByTestId('select-account-0').click(); + await page.getByTestId('select-account-1').click(); + + await page + .getByRole('button', { name: 'Recover selected accounts' }) + .click(); + + await page.goto(`chrome-extension://${extensionId}/popup.html`); + + await page.getByTestId('menu-open-icon').click(); + await page.getByText('All accounts').click(); + + await onboardingExpect( + page.getByText(DEFAULT_FIRST_ACCOUNT.accountName) + ).toBeVisible(); + await onboardingExpect( + page.getByText(DEFAULT_FIRST_ACCOUNT.truncatedPublicKey).nth(0) + ).toBeVisible(); + await onboardingExpect( + page.getByText(DEFAULT_SECOND_ACCOUNT.accountName) + ).toBeVisible(); + await onboardingExpect( + page.getByText(DEFAULT_SECOND_ACCOUNT.truncatedPublicKey).nth(0) + ).toBeVisible(); + } + ); + onboarding( + 'should recover one account via 12 words secret phrase', async ({ page, createOnboardingPassword, extensionId }) => { await createOnboardingPassword(); @@ -57,16 +132,102 @@ onboarding.describe('Onboarding UI: recover secret phrase flow', () => { .getByPlaceholder('e.g. Bobcat Lemon Blanket…') .fill(twelveWordsSecretPhrase); - await page.getByRole('button', { name: 'Recover my wallet' }).click(); + await page.getByRole('button', { name: 'Next' }).click(); + + await onboardingExpect( + page.getByText('Select accounts to recover') + ).toBeVisible(); + + await onboardingExpect( + page.getByRole('button', { name: 'Recover selected accounts' }) + ).toBeDisabled(); + + await page.getByTestId('select-account-0').click(); + + await onboardingExpect( + page.getByRole('button', { name: 'Recover selected accounts' }) + ).not.toBeDisabled(); + + await page + .getByRole('button', { name: 'Recover selected accounts' }) + .click(); + + await page.goto(`chrome-extension://${extensionId}/popup.html`); + + await page.getByTestId('menu-open-icon').click(); + await page.getByText('All accounts').click(); + + await onboardingExpect( + page.getByText(RECOVER_FIRST_ACCOUNT_FROM_TWELVE_WORDS.accountName) + ).toBeVisible(); + await onboardingExpect( + page + .getByText(RECOVER_FIRST_ACCOUNT_FROM_TWELVE_WORDS.truncatedPublicKey) + .nth(0) + ).toBeVisible(); + } + ); + onboarding( + 'should recover two accounts via 12 words secret phrase', + async ({ page, createOnboardingPassword, extensionId }) => { + await createOnboardingPassword(); + + await page + .getByRole('button', { + name: 'Import an existing secret recovery phrase' + }) + .click(); + + await onboardingExpect( + page.getByText('Please enter your secret recovery phrase') + ).toBeVisible(); + + await page + .getByPlaceholder('e.g. Bobcat Lemon Blanket…') + .fill(twelveWordsSecretPhrase); + + await page.getByRole('button', { name: 'Next' }).click(); + + await onboardingExpect( + page.getByText('Select accounts to recover') + ).toBeVisible(); + + await onboardingExpect( + page.getByRole('button', { name: 'Recover selected accounts' }) + ).toBeDisabled(); + + await page.getByTestId('select-account-0').click(); + await page.getByTestId('select-account-1').click(); + + await onboardingExpect( + page.getByRole('button', { name: 'Recover selected accounts' }) + ).not.toBeDisabled(); + + await page + .getByRole('button', { name: 'Recover selected accounts' }) + .click(); await page.goto(`chrome-extension://${extensionId}/popup.html`); + await page.getByTestId('menu-open-icon').click(); + await page.getByText('All accounts').click(); + + await onboardingExpect( + page.getByText(RECOVER_FIRST_ACCOUNT_FROM_TWELVE_WORDS.accountName) + ).toBeVisible(); + await onboardingExpect( + page + .getByText(RECOVER_FIRST_ACCOUNT_FROM_TWELVE_WORDS.truncatedPublicKey) + .nth(0) + ).toBeVisible(); await onboardingExpect( - page.getByText(RECOVER_ACCOUNT_FROM_TWELVE_WORDS.accountName) + page.getByText(RECOVER_SECOND_ACCOUNT_FROM_TWELVE_WORDS.accountName) ).toBeVisible(); await onboardingExpect( page - .getByText(RECOVER_ACCOUNT_FROM_TWELVE_WORDS.truncatedPublicKey) + .getByText( + RECOVER_SECOND_ACCOUNT_FROM_TWELVE_WORDS.truncatedPublicKey + ) .nth(0) ).toBeVisible(); } diff --git a/e2e-tests/popup/buy-cspr/buy-cspr.spec.ts b/e2e-tests/popup/buy-cspr/buy-cspr.spec.ts index 420f74246..6cd24a872 100644 --- a/e2e-tests/popup/buy-cspr/buy-cspr.spec.ts +++ b/e2e-tests/popup/buy-cspr/buy-cspr.spec.ts @@ -43,7 +43,6 @@ popup.describe('Popup UI: buy cspr', () => { await new Promise(r => setTimeout(r, 2000)); - console.log(torusPage.url()); popupExpect(torusPage.url()).toContain('https://app.topperpay.com/'); } ); @@ -90,7 +89,6 @@ popup.describe('Popup UI: buy cspr', () => { await new Promise(r => setTimeout(r, 2000)); - console.log(rampPage.url()); popupExpect(rampPage.url()).toContain('https://app.ramp.network/'); } ); @@ -117,6 +115,9 @@ popup.describe('Popup UI: buy cspr', () => { await popupPage.getByText('Ukraine').click(); + // wait until a modal window closed + await new Promise(r => setTimeout(r, 2000)); + await popupPage.getByRole('button', { name: 'Next' }).click(); await popupExpect( @@ -128,6 +129,9 @@ popup.describe('Popup UI: buy cspr', () => { await popupPage.getByText('UAH').click(); + // wait until a modal window closed + await new Promise(r => setTimeout(r, 2000)); + await popupPage.getByRole('button', { name: 'Next' }).click(); await popupExpect( diff --git a/e2e-tests/popup/signature-request-scenarios/signature-request-scenarios.spec.ts b/e2e-tests/popup/signature-request-scenarios/signature-request-scenarios.spec.ts index 26707e9a3..7d8929f11 100644 --- a/e2e-tests/popup/signature-request-scenarios/signature-request-scenarios.spec.ts +++ b/e2e-tests/popup/signature-request-scenarios/signature-request-scenarios.spec.ts @@ -1,8 +1,8 @@ import { DEFAULT_FIRST_ACCOUNT, - NEW_VALIDATOR, + NEW_VALIDATOR_FOR_SIGNATURE_REQUEST, PLAYGROUND_URL, - VALIDATOR + VALIDATOR_FOR_SIGNATURE_REQUEST } from '../../constants'; import { popup, popupExpect } from '../../fixtures'; @@ -35,7 +35,9 @@ popup.describe('Popup UI: signature request scenarios', () => { signatureRequestPage.getByText('Recipient (Key)') ).toBeVisible(); await popupExpect( - signatureRequestPage.getByText(VALIDATOR.truncatedPublicKey) + signatureRequestPage.getByText( + VALIDATOR_FOR_SIGNATURE_REQUEST.truncatedPublicKey + ) ).toBeVisible(); await popupExpect(signatureRequestPage.getByText('Amount')).toBeVisible(); await popupExpect(signatureRequestPage.getByText('2.5 CSPR')).toBeVisible(); @@ -81,10 +83,12 @@ popup.describe('Popup UI: signature request scenarios', () => { .nth(2) ).toBeVisible(); await popupExpect( - signatureRequestPage.getByText(VALIDATOR.name) + signatureRequestPage.getByText(VALIDATOR_FOR_SIGNATURE_REQUEST.name) ).toBeVisible(); await popupExpect( - signatureRequestPage.getByText(VALIDATOR.truncatedPublicKey) + signatureRequestPage.getByText( + VALIDATOR_FOR_SIGNATURE_REQUEST.truncatedPublicKey + ) ).toBeVisible(); await popupExpect(signatureRequestPage.getByText('Amount')).toBeVisible(); await popupExpect( @@ -130,10 +134,12 @@ popup.describe('Popup UI: signature request scenarios', () => { .nth(2) ).toBeVisible(); await popupExpect( - signatureRequestPage.getByText(VALIDATOR.name) + signatureRequestPage.getByText(VALIDATOR_FOR_SIGNATURE_REQUEST.name) ).toBeVisible(); await popupExpect( - signatureRequestPage.getByText(VALIDATOR.truncatedPublicKey) + signatureRequestPage.getByText( + VALIDATOR_FOR_SIGNATURE_REQUEST.truncatedPublicKey + ) ).toBeVisible(); await popupExpect(signatureRequestPage.getByText('Amount')).toBeVisible(); await popupExpect(signatureRequestPage.getByText('2.5 CSPR')).toBeVisible(); @@ -177,18 +183,24 @@ popup.describe('Popup UI: signature request scenarios', () => { .nth(2) ).toBeVisible(); await popupExpect( - signatureRequestPage.getByText(VALIDATOR.name, { exact: true }) + signatureRequestPage.getByText(VALIDATOR_FOR_SIGNATURE_REQUEST.name, { + exact: true + }) ).toBeVisible(); await popupExpect( - signatureRequestPage.getByText(VALIDATOR.truncatedPublicKey) + signatureRequestPage.getByText( + VALIDATOR_FOR_SIGNATURE_REQUEST.truncatedPublicKey + ) ).toBeVisible(); await popupExpect(signatureRequestPage.getByText('Amount')).toBeVisible(); await popupExpect(signatureRequestPage.getByText('2.5 CSPR')).toBeVisible(); await popupExpect( - signatureRequestPage.getByText(NEW_VALIDATOR.name) + signatureRequestPage.getByText(NEW_VALIDATOR_FOR_SIGNATURE_REQUEST.name) ).toBeVisible(); await popupExpect( - signatureRequestPage.getByText(NEW_VALIDATOR.truncatedPublicKey) + signatureRequestPage.getByText( + NEW_VALIDATOR_FOR_SIGNATURE_REQUEST.truncatedPublicKey + ) ).toBeVisible(); page.on('dialog', async dialog => { diff --git a/e2e-tests/popup/stakes/delegate.spec.ts b/e2e-tests/popup/stakes/delegate.spec.ts new file mode 100644 index 000000000..01a2bc196 --- /dev/null +++ b/e2e-tests/popup/stakes/delegate.spec.ts @@ -0,0 +1,64 @@ +import { RPC_RESPONSE, URLS, VALIDATOR_FOR_STAKE } from '../../constants'; +import { popup, popupExpect } from '../../fixtures'; + +popup.describe('Popup UI: Delegation', () => { + popup( + 'should made a successful delegation', + async ({ popupPage, unlockVault }) => { + await unlockVault(); + + await popupPage.route(URLS.rpc, route => + route.fulfill(RPC_RESPONSE.success) + ); + + await popupPage.getByText('More').click(); + + await popupPage.getByText('Delegate', { exact: true }).click(); + + await popupExpect( + popupPage.getByRole('heading', { name: 'Delegate' }) + ).toBeVisible(); + + await popupPage + .getByPlaceholder('Validator public address', { exact: true }) + .fill(VALIDATOR_FOR_STAKE.publicKey); + + await new Promise(r => setTimeout(r, 2000)); + + await popupPage + .getByText(VALIDATOR_FOR_STAKE.truncatedPublicKey, { exact: true }) + .click(); + + await popupPage.getByRole('button', { name: 'Next' }).click(); + + await popupExpect( + popupPage.getByRole('heading', { name: 'Delegate amount' }) + ).toBeVisible(); + + await popupPage.getByPlaceholder('0.00', { exact: true }).fill('500'); + + await popupPage.getByRole('button', { name: 'Next' }).click(); + + // Scroll to the bottom + await popupPage.evaluate(() => { + const container = document.querySelector('#ms-container'); + + container?.scrollTo(0, 1000); + }); + + await popupExpect( + popupPage.getByRole('button', { name: 'Confirm delegation' }) + ).not.toBeDisabled(); + + await popupPage + .getByRole('button', { name: 'Confirm delegation' }) + .click(); + + await popupExpect( + popupPage.getByRole('heading', { + name: 'You’ve submitted a delegation' + }) + ).toBeVisible(); + } + ); +}); diff --git a/e2e-tests/popup/stakes/redelagation.spec.ts b/e2e-tests/popup/stakes/redelagation.spec.ts new file mode 100644 index 000000000..dc7b53f52 --- /dev/null +++ b/e2e-tests/popup/stakes/redelagation.spec.ts @@ -0,0 +1,95 @@ +import { + NEW_VALIDATOR_FOR_STAKE, + RPC_RESPONSE, + URLS, + VALIDATOR_FOR_STAKE +} from '../../constants'; +import { popup, popupExpect } from '../../fixtures'; + +popup.describe('Popup UI: Redelegation', () => { + popup( + 'should made a successful redelegation', + async ({ popupPage, unlockVault }) => { + await unlockVault(); + + await popupPage.route(URLS.rpc, route => + route.fulfill(RPC_RESPONSE.success) + ); + + await popupPage.getByText('More').click(); + + await popupPage.getByText('Redelegate', { exact: true }).click(); + + await popupExpect( + popupPage.getByRole('heading', { name: 'Redelegate' }) + ).toBeVisible(); + + await new Promise(r => setTimeout(r, 2000)); + + await popupPage + .getByText(VALIDATOR_FOR_STAKE.truncatedPublicKey, { exact: true }) + .click(); + + await popupPage.getByRole('button', { name: 'Next' }).click(); + + await popupExpect( + popupPage.getByRole('heading', { name: 'Redelegate amount' }) + ).toBeVisible(); + + await popupPage.getByPlaceholder('0.00', { exact: true }).fill('500'); + + await popupPage.getByRole('button', { name: 'Next' }).click(); + + await popupPage.getByText('Delegate', { exact: true }).click(); + + await popupExpect( + popupPage.getByRole('heading', { name: 'Delegate' }) + ).toBeVisible(); + + await popupPage + .getByPlaceholder('Validator public address', { exact: true }) + .fill(NEW_VALIDATOR_FOR_STAKE.publicKey); + + await new Promise(r => setTimeout(r, 2000)); + + await popupPage + .getByText(NEW_VALIDATOR_FOR_STAKE.truncatedPublicKey, { exact: true }) + .click(); + + await popupPage.getByRole('button', { name: 'Next' }).click(); + + await popupExpect( + popupPage.getByRole('button', { name: 'Confirm redelegation' }) + ).toBeDisabled(); + + await popupExpect( + popupPage.getByText(VALIDATOR_FOR_STAKE.publicKey) + ).toBeVisible(); + + await popupExpect( + popupPage.getByText(NEW_VALIDATOR_FOR_STAKE.publicKey) + ).toBeVisible(); + + // Scroll to the bottom + await popupPage.evaluate(() => { + const container = document.querySelector('#ms-container'); + + container?.scrollTo(0, 1000); + }); + + await popupExpect( + popupPage.getByRole('button', { name: 'Confirm redelegation' }) + ).not.toBeDisabled(); + + await popupPage + .getByRole('button', { name: 'Confirm redelegation' }) + .click(); + + await popupExpect( + popupPage.getByRole('heading', { + name: 'You’ve submitted a redelegation' + }) + ).toBeVisible(); + } + ); +}); diff --git a/e2e-tests/popup/stakes/undelegate.spec.ts b/e2e-tests/popup/stakes/undelegate.spec.ts new file mode 100644 index 000000000..7ff608e24 --- /dev/null +++ b/e2e-tests/popup/stakes/undelegate.spec.ts @@ -0,0 +1,64 @@ +import { RPC_RESPONSE, URLS, VALIDATOR_FOR_STAKE } from '../../constants'; +import { popup, popupExpect } from '../../fixtures'; + +popup.describe('Popup UI: Undelegation', () => { + popup( + 'should made a successful undelegation', + async ({ popupPage, unlockVault }) => { + await unlockVault(); + + await popupPage.route(URLS.rpc, route => + route.fulfill(RPC_RESPONSE.success) + ); + + await popupPage.getByText('More').click(); + + await popupPage.getByText('Undelegate', { exact: true }).click(); + + await popupExpect( + popupPage.getByRole('heading', { name: 'Undelegate' }) + ).toBeVisible(); + + await new Promise(r => setTimeout(r, 2000)); + + await popupPage + .getByText(VALIDATOR_FOR_STAKE.truncatedPublicKey, { exact: true }) + .click(); + + await popupPage.getByRole('button', { name: 'Next' }).click(); + + await popupExpect( + popupPage.getByRole('heading', { name: 'Undelegate amount' }) + ).toBeVisible(); + + await popupPage.getByPlaceholder('0.00', { exact: true }).fill('500'); + + await popupPage.getByRole('button', { name: 'Next' }).click(); + + await popupExpect( + popupPage.getByText(VALIDATOR_FOR_STAKE.publicKey) + ).toBeVisible(); + + // Scroll to the bottom + await popupPage.evaluate(() => { + const container = document.querySelector('#ms-container'); + + container?.scrollTo(0, 1000); + }); + + await popupExpect( + popupPage.getByRole('button', { name: 'Confirm undelegation' }) + ).not.toBeDisabled(); + + await popupPage + .getByRole('button', { name: 'Confirm undelegation' }) + .click(); + + await popupExpect( + popupPage.getByRole('heading', { + name: 'You’ve submitted an undelegation' + }) + ).toBeVisible(); + } + ); +}); diff --git a/e2e-tests/popup/transfers/casper-native-transfer.spec.ts b/e2e-tests/popup/transfers/casper-native-transfer.spec.ts new file mode 100644 index 000000000..75c2103b2 --- /dev/null +++ b/e2e-tests/popup/transfers/casper-native-transfer.spec.ts @@ -0,0 +1,146 @@ +import { DEFAULT_SECOND_ACCOUNT, RPC_RESPONSE, URLS } from '../../constants'; +import { popup, popupExpect } from '../../fixtures'; + +popup.describe('Popup UI: Casper Native Transfer', () => { + popup( + 'should made a successful transfer', + async ({ popupPage, unlockVault }) => { + await unlockVault(); + + await popupPage.route(URLS.rpc, route => + route.fulfill(RPC_RESPONSE.success) + ); + + await new Promise(r => setTimeout(r, 5000)); + + await popupPage.getByText('Send').click(); + + await popupExpect( + popupPage.getByRole('heading', { name: 'Select token and account' }) + ).toBeVisible(); + + await popupPage.getByRole('button', { name: 'Next' }).click(); + + await popupExpect( + popupPage.getByRole('heading', { name: 'Specify recipient' }) + ).toBeVisible(); + + await popupExpect( + popupPage.getByRole('button', { name: 'Next' }) + ).toBeDisabled(); + + await popupPage + .getByPlaceholder('Public key', { exact: true }) + .fill(DEFAULT_SECOND_ACCOUNT.publicKey); + + await popupPage.getByRole('button', { name: 'Next' }).click(); + + await popupExpect( + popupPage.getByRole('heading', { name: 'Enter amount' }) + ).toBeVisible(); + + await popupPage.getByRole('button', { name: 'Next' }).click(); + + await popupExpect( + popupPage.getByRole('heading', { name: 'Confirm sending' }) + ).toBeVisible(); + + await popupExpect( + popupPage.getByText(DEFAULT_SECOND_ACCOUNT.publicKey) + ).toBeVisible(); + + await popupExpect( + popupPage.getByRole('button', { name: 'Confirm send' }) + ).toBeDisabled(); + + // Scroll to the bottom + await popupPage.evaluate(() => { + const container = document.querySelector('#ms-container'); + + container?.scrollTo(0, 1000); + }); + + await popupExpect( + popupPage.getByRole('button', { name: 'Confirm send' }) + ).not.toBeDisabled(); + + await popupPage.getByRole('button', { name: 'Confirm send' }).click(); + + await popupExpect( + popupPage.getByRole('heading', { name: 'You submitted a transaction' }) + ).toBeVisible(); + + await popupPage.getByRole('button', { name: 'Done' }).click(); + } + ); + + popup('should made a failed transfer', async ({ popupPage, unlockVault }) => { + await unlockVault(); + + await popupPage.route(URLS.rpc, route => + route.fulfill(RPC_RESPONSE.failure) + ); + + await new Promise(r => setTimeout(r, 5000)); + + await popupPage.getByText('Send').click(); + + await popupExpect( + popupPage.getByRole('heading', { name: 'Select token and account' }) + ).toBeVisible(); + + await popupPage.getByRole('button', { name: 'Next' }).click(); + + await popupExpect( + popupPage.getByRole('heading', { name: 'Specify recipient' }) + ).toBeVisible(); + + await popupExpect( + popupPage.getByRole('button', { name: 'Next' }) + ).toBeDisabled(); + + await popupPage + .getByPlaceholder('Public key', { exact: true }) + .fill(DEFAULT_SECOND_ACCOUNT.publicKey); + + await popupPage.getByRole('button', { name: 'Next' }).click(); + + await popupExpect( + popupPage.getByRole('heading', { name: 'Enter amount' }) + ).toBeVisible(); + + await popupPage.getByRole('button', { name: 'Next' }).click(); + + await popupExpect( + popupPage.getByRole('heading', { name: 'Confirm sending' }) + ).toBeVisible(); + + await popupExpect( + popupPage.getByText(DEFAULT_SECOND_ACCOUNT.publicKey) + ).toBeVisible(); + + await popupExpect( + popupPage.getByRole('button', { name: 'Confirm send' }) + ).toBeDisabled(); + + // Scroll to the bottom + await popupPage.evaluate(() => { + const container = document.querySelector('#ms-container'); + + container?.scrollTo(0, 1000); + }); + + await popupExpect( + popupPage.getByRole('button', { name: 'Confirm send' }) + ).not.toBeDisabled(); + + await popupPage.getByRole('button', { name: 'Confirm send' }).click(); + + await popupExpect( + popupPage.getByRole('heading', { name: 'Error message' }) + ).toBeVisible(); + await popupExpect(popupPage.getByText('Error description')).toBeVisible(); + + await popupPage.getByRole('button', { name: 'Close' }).click(); + }); +}); diff --git a/e2e-tests/popup/transfers/erc-20-transfer.spec.ts b/e2e-tests/popup/transfers/erc-20-transfer.spec.ts new file mode 100644 index 000000000..7bfd4ae8e --- /dev/null +++ b/e2e-tests/popup/transfers/erc-20-transfer.spec.ts @@ -0,0 +1,185 @@ +import { DEFAULT_SECOND_ACCOUNT } from '../../constants'; +import { popup, popupExpect } from '../../fixtures'; + +popup.describe('Popup UI: ERC-20 transfer', () => { + popup( + 'should made a successful transfer', + async ({ unlockVault, popupPage }) => { + await unlockVault(); + + await popupPage.route('https://node.testnet.cspr.cloud/rpc', route => + route.fulfill({ + status: 200, + body: JSON.stringify({ + jsonrpc: '2.0', + id: 1717761373590, + result: { + api_version: '1.5.6', + deploy_hash: 'deploy hash' + } + }) + }) + ); + + await new Promise(r => setTimeout(r, 5000)); + + await popupPage.getByText('Send').click(); + + await popupExpect( + popupPage.getByRole('heading', { name: 'Select token and account' }) + ).toBeVisible(); + + await popupPage.getByTestId('token-row').click(); + + await popupPage.getByText('CSPR.click tests').click(); + + // wait until a modal window closed + await new Promise(r => setTimeout(r, 2000)); + + await popupPage.getByRole('button', { name: 'Next' }).click(); + + await popupExpect( + popupPage.getByRole('heading', { name: 'Specify recipient' }) + ).toBeVisible(); + + await popupExpect( + popupPage.getByRole('button', { name: 'Next' }) + ).toBeDisabled(); + + await popupPage + .getByPlaceholder('Public key', { exact: true }) + .fill(DEFAULT_SECOND_ACCOUNT.publicKey); + + await popupPage.getByRole('button', { name: 'Next' }).click(); + + await popupExpect( + popupPage.getByRole('heading', { name: 'Enter amount' }) + ).toBeVisible(); + + await popupPage.getByPlaceholder('0.00', { exact: true }).fill('10'); + + await popupPage.getByRole('button', { name: 'Next' }).click(); + + await popupExpect( + popupPage.getByRole('heading', { name: 'Confirm sending' }) + ).toBeVisible(); + + await popupExpect( + popupPage.getByText(DEFAULT_SECOND_ACCOUNT.publicKey) + ).toBeVisible(); + + await popupExpect( + popupPage.getByRole('button', { name: 'Confirm send' }) + ).toBeDisabled(); + + // Scroll to the bottom + await popupPage.evaluate(() => { + const container = document.querySelector('#ms-container'); + + container?.scrollTo(0, 1000); + }); + + await popupExpect( + popupPage.getByRole('button', { name: 'Confirm send' }) + ).not.toBeDisabled(); + + await popupPage.getByRole('button', { name: 'Confirm send' }).click(); + + await popupExpect( + popupPage.getByRole('heading', { name: 'You submitted a transaction' }) + ).toBeVisible(); + + await popupPage.getByRole('button', { name: 'Done' }).click(); + } + ); + + popup('should made a failed transfer', async ({ unlockVault, popupPage }) => { + await unlockVault(); + + await popupPage.route('https://node.testnet.cspr.cloud/rpc', route => + route.fulfill({ + status: 500, + body: JSON.stringify({ + jsonrpc: '2.0', + id: 1717761373590, + error: { + code: '', + data: 'Error description', + message: 'Error message' + } + }) + }) + ); + + await new Promise(r => setTimeout(r, 5000)); + + await popupPage.getByText('Send').click(); + + await popupExpect( + popupPage.getByRole('heading', { name: 'Select token and account' }) + ).toBeVisible(); + + await popupPage.getByTestId('token-row').click(); + + await popupPage.getByText('CSPR.click tests').click(); + + // wait until a modal window closed + await new Promise(r => setTimeout(r, 2000)); + + await popupPage.getByRole('button', { name: 'Next' }).click(); + + await popupExpect( + popupPage.getByRole('heading', { name: 'Specify recipient' }) + ).toBeVisible(); + + await popupExpect( + popupPage.getByRole('button', { name: 'Next' }) + ).toBeDisabled(); + + await popupPage + .getByPlaceholder('Public key', { exact: true }) + .fill(DEFAULT_SECOND_ACCOUNT.publicKey); + + await popupPage.getByRole('button', { name: 'Next' }).click(); + + await popupExpect( + popupPage.getByRole('heading', { name: 'Enter amount' }) + ).toBeVisible(); + + await popupPage.getByPlaceholder('0.00', { exact: true }).fill('10'); + + await popupPage.getByRole('button', { name: 'Next' }).click(); + + await popupExpect( + popupPage.getByRole('heading', { name: 'Confirm sending' }) + ).toBeVisible(); + + await popupExpect( + popupPage.getByText(DEFAULT_SECOND_ACCOUNT.publicKey) + ).toBeVisible(); + + await popupExpect( + popupPage.getByRole('button', { name: 'Confirm send' }) + ).toBeDisabled(); + + // Scroll to the bottom + await popupPage.evaluate(() => { + const container = document.querySelector('#ms-container'); + + container?.scrollTo(0, 1000); + }); + + await popupExpect( + popupPage.getByRole('button', { name: 'Confirm send' }) + ).not.toBeDisabled(); + + await popupPage.getByRole('button', { name: 'Confirm send' }).click(); + + await popupExpect( + popupPage.getByRole('heading', { name: 'Error message' }) + ).toBeVisible(); + await popupExpect(popupPage.getByText('Error description')).toBeVisible(); + + await popupPage.getByRole('button', { name: 'Close' }).click(); + }); +}); diff --git a/e2e-tests/popup/transfers/nft-transfer.spec.ts b/e2e-tests/popup/transfers/nft-transfer.spec.ts new file mode 100644 index 000000000..3a2c5eb98 --- /dev/null +++ b/e2e-tests/popup/transfers/nft-transfer.spec.ts @@ -0,0 +1,65 @@ +import { DEFAULT_SECOND_ACCOUNT, RPC_RESPONSE, URLS } from '../../constants'; +import { popup, popupExpect } from '../../fixtures'; + +popup.describe('Popup UI: NFT Transfer', () => { + popup( + 'should made a successful nft transfer', + async ({ popupPage, unlockVault }) => { + await unlockVault(); + + await popupPage.route(URLS.rpc, route => + route.fulfill(RPC_RESPONSE.success) + ); + + await new Promise(r => setTimeout(r, 2000)); + + await popupPage.getByText('NFTs').click(); + + await new Promise(r => setTimeout(r, 2000)); + + await popupPage.getByTestId('nft-token-card').click(); + + await popupExpect( + popupPage.getByRole('heading', { name: 'west' }) + ).toBeVisible(); + + await popupPage.getByText('Send').click(); + + await popupExpect( + popupPage.getByRole('heading', { name: 'Review details' }) + ).toBeVisible(); + + await popupPage.getByRole('button', { name: 'Next' }).click(); + + await popupExpect( + popupPage.getByRole('heading', { name: 'Select recipient' }) + ).toBeVisible(); + + await popupExpect( + popupPage.getByRole('button', { name: 'Next' }) + ).toBeDisabled(); + + await popupPage + .getByPlaceholder('Public key', { exact: true }) + .fill(DEFAULT_SECOND_ACCOUNT.publicKey); + + await popupPage.getByRole('button', { name: 'Next' }).click(); + + await popupExpect( + popupPage.getByRole('heading', { name: 'Confirm sending' }) + ).toBeVisible(); + + await popupExpect( + popupPage.getByText(DEFAULT_SECOND_ACCOUNT.publicKey) + ).toBeVisible(); + + await popupPage.getByRole('button', { name: 'Confirm send' }).click(); + + await popupExpect( + popupPage.getByRole('heading', { name: 'You submitted a transaction' }) + ).toBeVisible(); + + await popupPage.getByRole('button', { name: 'Done' }).click(); + } + ); +}); diff --git a/package-lock.json b/package-lock.json index 52f51f57e..0d8b27cda 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "Casper Wallet", - "version": "1.10.0", + "version": "1.11.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "Casper Wallet", - "version": "1.10.0", + "version": "1.11.0", "dependencies": { "@formatjs/intl": "2.6.2", "@hookform/resolvers": "2.9.10", @@ -62,7 +62,7 @@ "@babel/preset-react": "7.18.6", "@babel/preset-typescript": "^7.23.3", "@playwright/test": "^1.39.0", - "@redux-devtools/cli": "^3.0.1", + "@redux-devtools/cli": "^4.0.0", "@redux-devtools/remote": "^0.9.1", "@testing-library/dom": "9.3.4", "@testing-library/jest-dom": "^6.4.2", @@ -87,7 +87,7 @@ "css-loader": "6.8.1", "eslint": "8.49.0", "eslint-config-airbnb": "^19.0.4", - "eslint-config-prettier": "^8.3.0", + "eslint-config-prettier": "^9.1.0", "eslint-config-react-app": "^7.0.0", "eslint-import-resolver-typescript": "^3.6.1", "eslint-plugin-flowtype": "^8.0.3", @@ -99,7 +99,7 @@ "file-loader": "^6.2.0", "fs-extra": "11.1.1", "html-loader": "4.2.0", - "html-webpack-plugin": "^5.5.3", + "html-webpack-plugin": "^5.6.0", "husky": "8.0.2", "i18next-conv": "14.0.0", "jest": "29.3.1", @@ -187,9 +187,9 @@ } }, "node_modules/@apollo/server": { - "version": "4.9.3", - "resolved": "https://registry.npmjs.org/@apollo/server/-/server-4.9.3.tgz", - "integrity": "sha512-U56Sx/UmzR3Es344hQ/Ptf2EJrH+kV4ZPoLmgGjWoiwf2wYQ/pRSvkSXgjOvoyE34wSa8Gh7f92ljfLfY+6q1w==", + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/@apollo/server/-/server-4.9.5.tgz", + "integrity": "sha512-eDBfArYbZaTm1AGa82M1aL7lOscVhnZsH85+OWmHMIR98qntzEjNpWpQPYDTru63Qxs4kHcY29NUx/kMGZfGEA==", "dev": true, "dependencies": { "@apollo/cache-control-types": "^1.0.3", @@ -2787,9 +2787,9 @@ "integrity": "sha512-igX9a37DR2ZPGYtV6suZ6whr8pTFtyHL3K/oLUotxpSVO2ASaprmAe2Dkq7tBo7CRY7MMDrAa9nuQP9/YG8FxQ==" }, "node_modules/@emotion/react": { - "version": "11.11.3", - "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.11.3.tgz", - "integrity": "sha512-Cnn0kuq4DoONOMcnoVsTOR8E+AdnKFf//6kUWc4LCdnxj31pZWn7rIULd6Y7/Js1PiPHzn7SKCM9vB/jBni8eA==", + "version": "11.11.4", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.11.4.tgz", + "integrity": "sha512-t8AjMlF0gHpvvxk5mAtCqR4vmxiGHCeJBaQO6gncUSdklELOgtwjerNY2yuJNfwnc6vi16U/+uMF+afIawJ9iw==", "dev": true, "dependencies": { "@babel/runtime": "^7.18.3", @@ -5027,83 +5027,84 @@ "dev": true }, "node_modules/@redux-devtools/cli": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@redux-devtools/cli/-/cli-3.0.1.tgz", - "integrity": "sha512-NARxbTvLQQkZ4if5zvQTkTyesKCsu9DpN9Uupz1Ib18lUEuuEYl3HKJFYQ/hScnxxMSin7KN8j8guHMVD16pqg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@redux-devtools/cli/-/cli-4.0.0.tgz", + "integrity": "sha512-MACJmhnNS8Q0P8ldYgH+p7//U6QdetQqnIS5KZRzd8sp4rYSegzTqsQuf4eGZ4PSzrVOoVezj9eH6ESZFm9/9Q==", "dev": true, "dependencies": { - "@apollo/server": "^4.9.1", - "@redux-devtools/app": "^3.0.0", - "@types/react": "^18.2.20", + "@apollo/server": "^4.9.5", + "@emotion/react": "^11.11.3", + "@redux-devtools/app": "^6.0.0", + "@types/react": "^18.2.46", "body-parser": "^1.20.2", "chalk": "^5.3.0", "cors": "^2.8.5", "cross-spawn": "^7.0.3", - "electron": "^25.6.0", + "electron": "^27.2.0", "express": "^4.18.2", "get-port": "^7.0.0", - "graphql": "^16.8.0", - "knex": "^2.5.1", + "graphql": "^16.8.1", + "knex": "^3.1.0", "lodash-es": "^4.17.21", "minimist": "^1.2.8", "morgan": "^1.10.0", - "open": "^9.1.0", + "open": "^10.0.2", "react": "^18.2.0", "react-dom": "^18.2.0", "react-is": "^18.2.0", "semver": "^7.5.4", - "socketcluster-server": "^17.4.1", + "socketcluster-server": "^19.0.1", "sqlite3": "^5.1.6", "styled-components": "^5.3.11", - "uuid": "^9.0.0" + "uuid": "^9.0.1" }, "bin": { "redux-devtools": "bin/redux-devtools.js" }, "engines": { - "node": "^16.13.0 || >= 18.12.0" + "node": ">= 18.12.0" } }, "node_modules/@redux-devtools/cli/node_modules/@babel/code-frame": { - "version": "8.0.0-alpha.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-8.0.0-alpha.6.tgz", - "integrity": "sha512-w29LUiFqVMe+Ybp/+xtQKAp6rpxjHxtCW909I0OMHvFKzrGB0K+1yQGRzXhTqrd4f7ZRRgzkAxdr0EOEbHt3dA==", + "version": "8.0.0-alpha.8", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-8.0.0-alpha.8.tgz", + "integrity": "sha512-PaXRlxddNNx+S+OZ/laLOqhiqaDgBQCvxR/Z0CHcpXgPRmtBNbRxvWhf91+YtST1O+O6j2K0F4bb5DvA3Hr4gQ==", "dev": true, "dependencies": { - "@babel/highlight": "^8.0.0-alpha.6", - "chalk": "^5.3.0" + "@babel/highlight": "^8.0.0-alpha.8", + "picocolors": "^1.0.0" }, "engines": { "node": "^16.20.0 || ^18.16.0 || >=20.0.0" } }, "node_modules/@redux-devtools/cli/node_modules/@babel/helper-validator-identifier": { - "version": "8.0.0-alpha.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.0-alpha.6.tgz", - "integrity": "sha512-a0YnAx4TJCgds07M2v0A3WeyZUfls2YOgNHCb1akGKlz5KdmTCqf2N2hw86YhjY6oDqb6Omb8gFKz/3TOKRevQ==", + "version": "8.0.0-alpha.8", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.0-alpha.8.tgz", + "integrity": "sha512-7uCkkKAnoPa864zeoJ82mhXNVhRtvVbquSZ+i98qG4jaUu3yRe3ZKWV1K075FTthM4gNgRVMWTK/KjrSuX9w+Q==", "dev": true, "engines": { "node": "^16.20.0 || ^18.16.0 || >=20.0.0" } }, "node_modules/@redux-devtools/cli/node_modules/@babel/highlight": { - "version": "8.0.0-alpha.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-8.0.0-alpha.6.tgz", - "integrity": "sha512-YzJ5blZXak7Gk6ZgV9a4B9bAkGzl594ZxmBaFFZkstA5hE1iOBTlUW4tkGKz2uomL7FLLW4qHJF3QjQLHKi7iw==", + "version": "8.0.0-alpha.8", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-8.0.0-alpha.8.tgz", + "integrity": "sha512-1xw7Z/AMD4ZJxxktv1sXClheNGab1BAU11SND6AiwUyhIAOVrvQ9QiOBd8I/iMXdXtANCNyu0YLswn3TNlcRZw==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^8.0.0-alpha.6", - "chalk": "^5.3.0", - "js-tokens": "^8.0.0" + "@babel/helper-validator-identifier": "^8.0.0-alpha.8", + "js-tokens": "^8.0.0", + "picocolors": "^1.0.0" }, "engines": { "node": "^16.20.0 || ^18.16.0 || >=20.0.0" } }, "node_modules/@redux-devtools/cli/node_modules/@babel/runtime": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.9.tgz", - "integrity": "sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.6.tgz", + "integrity": "sha512-Ja18XcETdEl5mzzACGd+DKgaGJzPTCow7EglgwTmHdwokzDFYh/MHua6lU6DV/hjF2IaOJ4oX2nqnjG7RElKOw==", "dev": true, "dependencies": { "regenerator-runtime": "^0.14.0" @@ -5113,252 +5114,290 @@ } }, "node_modules/@redux-devtools/cli/node_modules/@redux-devtools/app": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@redux-devtools/app/-/app-3.0.0.tgz", - "integrity": "sha512-3mn+cj3KhMRSa0EPxlRD4EnfFFMBsM1nKJ+g/6zB4iRyk23befz4YzwFG3KXkwyVmZCooCu3DjHkQhUOx4xxQA==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.22.10", - "@redux-devtools/chart-monitor": "^4.0.0", - "@redux-devtools/core": "^3.13.0", - "@redux-devtools/inspector-monitor": "^4.0.0", - "@redux-devtools/inspector-monitor-test-tab": "^2.0.0", - "@redux-devtools/inspector-monitor-trace-tab": "^2.0.0", - "@redux-devtools/log-monitor": "^4.0.2", - "@redux-devtools/rtk-query-monitor": "^3.1.1", - "@redux-devtools/slider-monitor": "^4.0.0", - "@redux-devtools/ui": "^1.3.0", - "@reduxjs/toolkit": "^1.9.5", - "@types/prop-types": "^15.7.5", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@redux-devtools/app/-/app-6.0.0.tgz", + "integrity": "sha512-jR6Nm/Pe/ImQ2XmJC1JIyVduQ/3J4bXQhtvoH/pw8WuVJrQvFzhfY5McS+LbHB6H+Vjfhq7mq3u1T2dcQ6II3g==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.23.5", + "@redux-devtools/chart-monitor": "^5.0.0", + "@redux-devtools/core": "^4.0.0", + "@redux-devtools/inspector-monitor": "^6.0.0", + "@redux-devtools/inspector-monitor-test-tab": "^4.0.0", + "@redux-devtools/inspector-monitor-trace-tab": "^4.0.0", + "@redux-devtools/log-monitor": "^5.0.0", + "@redux-devtools/rtk-query-monitor": "^5.0.0", + "@redux-devtools/slider-monitor": "^5.0.0", + "@redux-devtools/ui": "^1.3.1", + "@reduxjs/toolkit": "^1.9.7", "d3-state-visualizer": "^2.0.0", "javascript-stringify": "^2.1.0", "jsan": "^3.1.14", "jsondiffpatch": "^0.5.0", "localforage": "^1.10.0", "lodash": "^4.17.21", - "prop-types": "^15.8.1", - "react-icons": "^4.10.1", + "react-icons": "^4.12.0", "react-is": "^18.2.0", - "react-redux": "^8.1.2", - "redux": "4.2.1", + "react-redux": "^8.1.3", + "redux": "^4.2.1", "redux-persist": "^6.0.0", "socketcluster-client": "^17.2.2" }, "peerDependencies": { + "@emotion/react": "^11.0.0", "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "@types/styled-components": "^5.1.26", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0", + "@types/styled-components": "^5.1.34", + "react": "^16.8.4 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.4 || ^17.0.0 || ^18.0.0", "styled-components": "^5.3.11" } }, - "node_modules/@redux-devtools/cli/node_modules/@redux-devtools/chart-monitor": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@redux-devtools/chart-monitor/-/chart-monitor-4.1.0.tgz", - "integrity": "sha512-fdW7DKEhCB9oumMTyhdrVGOgb/6lBioPveW3w0+u7MCZn2i32laZYzU9VT39sx+Fc2kwxzbYsLxFsU0tijGsXA==", + "node_modules/@redux-devtools/cli/node_modules/@redux-devtools/app/node_modules/@redux-devtools/chart-monitor": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@redux-devtools/chart-monitor/-/chart-monitor-5.0.2.tgz", + "integrity": "sha512-5S70WvWufMa8ybwpFqxGqA65Mb8xV7e1gviKnGp6KGDn+EYo2bu+O+o5ugt/4WW4z4HgeXkNl3DawwjTUNc9aw==", "dev": true, "dependencies": { - "@babel/runtime": "^7.23.2", - "@types/redux-devtools-themes": "^1.0.3", - "d3-state-visualizer": "^2.0.0", + "@babel/runtime": "^7.24.1", + "d3-state-visualizer": "^3.0.0", "deepmerge": "^4.3.1", - "redux-devtools-themes": "^1.0.0" + "react-base16-styling": "^0.10.0" }, "peerDependencies": { - "@redux-devtools/core": "^3.0.0", - "@types/react": "^16.3.0 || ^17.0.0 || ^18.0.0", - "react": "^16.3.0 || ^17.0.0 || ^18.0.0", + "@redux-devtools/core": "^4.0.0", + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.4 || ^17.0.0 || ^18.0.0", "redux": "^3.4.0 || ^4.0.0 || ^5.0.0" } }, - "node_modules/@redux-devtools/cli/node_modules/@redux-devtools/core": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/@redux-devtools/core/-/core-3.14.0.tgz", - "integrity": "sha512-OMPflPPCXR9L1rpfd7gwY31/EuqPyE9Of/5wZgDDzeisaENY5h/EfnAjnHRKr7NIx/yUIUX2DJs8NpmUOnANMg==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.23.2", - "@redux-devtools/instrument": "^2.2.0", - "@types/prop-types": "^15.7.10", - "lodash": "^4.17.21", - "prop-types": "^15.8.1" - }, - "peerDependencies": { - "react": "^0.14.9 || ^15.3.0 || ^16.0.0 || ^17.0.0 || ^18.0.0", - "react-redux": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0", - "redux": "^3.5.2 || ^4.0.0 || ^5.0.0" - } - }, - "node_modules/@redux-devtools/cli/node_modules/@redux-devtools/inspector-monitor": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@redux-devtools/inspector-monitor/-/inspector-monitor-4.1.0.tgz", - "integrity": "sha512-ga0rBdb8LeB7mN+X+/Pb0rx+yTPOsSHFFVxgcWag7XsBu+dXtujlRsCM++6LX6GjJGNzZAUsG4ZI4Wt0dG2CQg==", + "node_modules/@redux-devtools/cli/node_modules/@redux-devtools/app/node_modules/@redux-devtools/chart-monitor/node_modules/d3-state-visualizer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-state-visualizer/-/d3-state-visualizer-3.0.0.tgz", + "integrity": "sha512-69syDmi4b6sr9/UJUVpKTzFFx7jeI8AogEWv2qL/AWKky9b9NgqgMtSBbJNQdncq6epsOKP7a2MDun90taHtTQ==", "dev": true, "dependencies": { - "@babel/runtime": "^7.23.2", - "@dnd-kit/core": "^6.1.0", - "@dnd-kit/modifiers": "^7.0.0", - "@dnd-kit/sortable": "^8.0.0", - "@dnd-kit/utilities": "^3.2.2", - "@types/lodash": "^4.14.201", - "@types/prop-types": "^15.7.10", - "@types/redux-devtools-themes": "^1.0.3", - "dateformat": "^5.0.3", - "hex-rgba": "^1.0.2", - "immutable": "^4.3.4", - "javascript-stringify": "^2.1.0", - "jsondiffpatch": "^0.5.0", - "jss": "^10.10.0", - "jss-preset-default": "^10.10.0", - "lodash.debounce": "^4.0.8", - "prop-types": "^15.8.1", - "react-base16-styling": "^0.9.1", - "react-json-tree": "^0.18.0", - "redux-devtools-themes": "^1.0.0" - }, - "peerDependencies": { - "@redux-devtools/core": "^3.0.0", - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0", - "redux": "^3.4.0 || ^4.0.0 || ^5.0.0" + "@types/d3": "^7.4.3", + "d3": "^7.9.0", + "d3tooltip": "^4.0.0", + "deepmerge": "^4.3.1", + "map2tree": "^4.0.0", + "ramda": "^0.29.1" } }, - "node_modules/@redux-devtools/cli/node_modules/@redux-devtools/inspector-monitor-test-tab": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@redux-devtools/inspector-monitor-test-tab/-/inspector-monitor-test-tab-2.1.0.tgz", - "integrity": "sha512-wQ8dnB1F7RZn4XFoKYVPUPt996CIGWG1/ndQwGkXPvHg+P32QHUHsrhR+QDpn6Z1dMamycDjqijS2KDkQA/MDg==", + "node_modules/@redux-devtools/cli/node_modules/@redux-devtools/app/node_modules/@redux-devtools/inspector-monitor-test-tab": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@redux-devtools/inspector-monitor-test-tab/-/inspector-monitor-test-tab-4.0.0.tgz", + "integrity": "sha512-a5SuX6eyFvBAK9UcRW/n5K8nSISWR+UGW0+fZh+KP20naSgkvagrzC705NX8riCfJc8fr5csIYVYwmypMAZ2UA==", "dev": true, "dependencies": { - "@babel/runtime": "^7.23.2", + "@babel/runtime": "^7.23.5", "@redux-devtools/ui": "^1.3.1", - "@types/prop-types": "^15.7.10", "es6template": "^1.0.5", "javascript-stringify": "^2.1.0", "jsan": "^3.1.14", "object-path": "^0.11.8", - "prop-types": "^15.8.1", - "react-icons": "^4.11.0", + "react-icons": "^4.12.0", "simple-diff": "^1.7.2" }, "peerDependencies": { - "@redux-devtools/inspector-monitor": "^4.0.0", + "@emotion/react": "^11.0.0", + "@redux-devtools/inspector-monitor": "^6.0.0", "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "@types/styled-components": "^5.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0", + "@types/styled-components": "^5.1.34", + "react": "^16.8.4 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.4 || ^17.0.0 || ^18.0.0", "redux": "^3.4.0 || ^4.0.0 || ^5.0.0", - "styled-components": "^5.0.0" + "styled-components": "^5.3.11" } }, - "node_modules/@redux-devtools/cli/node_modules/@redux-devtools/inspector-monitor-trace-tab": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@redux-devtools/inspector-monitor-trace-tab/-/inspector-monitor-trace-tab-2.1.0.tgz", - "integrity": "sha512-Sac8iyYahyrq3k76kty1ebMVCp7Q1ABdBdzj8vJ5FSiaXOKU+HY1y4YS6Fb9ra7Bj5udIqzr/zdxprdPc7Yatg==", + "node_modules/@redux-devtools/cli/node_modules/@redux-devtools/app/node_modules/@redux-devtools/inspector-monitor-trace-tab": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@redux-devtools/inspector-monitor-trace-tab/-/inspector-monitor-trace-tab-4.0.1.tgz", + "integrity": "sha512-7tu/JU9wmpp74sBlBirgikrjJsx3IoJBKzUj9lO9ox9U9cJ/MhSo9B8ZAmLDI8EJa3CzxCcNQnDxW/Wucy17DQ==", "dev": true, "dependencies": { - "@babel/code-frame": "^8.0.0-alpha.4", - "@babel/runtime": "^7.23.2", - "@types/chrome": "^0.0.251", + "@babel/code-frame": "^8.0.0-alpha.7", + "@babel/runtime": "^7.24.1", + "@types/chrome": "^0.0.263", "anser": "^2.1.1", - "html-entities": "^2.4.0", + "html-entities": "^2.5.2", "path-browserify": "^1.0.1", - "redux-devtools-themes": "^1.0.0", + "react-base16-styling": "^0.10.0", "source-map": "^0.5.7" }, "peerDependencies": { - "@redux-devtools/inspector-monitor": "^4.0.0", + "@emotion/react": "^11.11.4", + "@redux-devtools/inspector-monitor": "^6.0.0", "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.4 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.4 || ^17.0.0 || ^18.0.0", "redux": "^3.4.0 || ^4.0.0 || ^5.0.0" } }, - "node_modules/@redux-devtools/cli/node_modules/@redux-devtools/log-monitor": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@redux-devtools/log-monitor/-/log-monitor-4.1.0.tgz", - "integrity": "sha512-2danca7yfQnuyzYxUVsEontmcRfQLFZgU8M33690JfvHlUV1OYjkNhbwKkMEso5py8sTd2EgoC2PGySoyupbUg==", + "node_modules/@redux-devtools/cli/node_modules/@redux-devtools/app/node_modules/@redux-devtools/log-monitor": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@redux-devtools/log-monitor/-/log-monitor-5.0.1.tgz", + "integrity": "sha512-NCuKr9hdruL01rbghKs/eD2lLcKhXmICXf2js2gTM9oDA2662+X9M9BahcJwjhkTUBxdsXbDQ5d/uGvxr530Vg==", "dev": true, "dependencies": { - "@babel/runtime": "^7.23.2", + "@babel/runtime": "^7.24.1", "@types/lodash.debounce": "^4.0.9", - "@types/prop-types": "^15.7.10", - "@types/redux-devtools-themes": "^1.0.3", "lodash.debounce": "^4.0.8", - "prop-types": "^15.8.1", - "react-json-tree": "^0.18.0", - "redux-devtools-themes": "^1.0.0" + "react-base16-styling": "^0.10.0", + "react-json-tree": "^0.19.0" }, "peerDependencies": { - "@redux-devtools/core": "^3.0.0", - "@types/react": "^16.3.0 || ^17.0.0 || ^18.0.0", - "react": "^16.3.0 || ^17.0.0 || ^18.0.0", + "@redux-devtools/core": "^4.0.0", + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.4 || ^17.0.0 || ^18.0.0", "redux": "^3.4.0 || ^4.0.0 || ^5.0.0" } }, - "node_modules/@redux-devtools/cli/node_modules/@redux-devtools/rtk-query-monitor": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@redux-devtools/rtk-query-monitor/-/rtk-query-monitor-3.2.0.tgz", - "integrity": "sha512-Td7oZUAO/TyxillkC2E33vYqfKEDFilJfLHLg8Na7KdfG3Y0JR5qmu0xypZDODeEsA5teHpkMnhI3O9ew/JqdQ==", + "node_modules/@redux-devtools/cli/node_modules/@redux-devtools/app/node_modules/@redux-devtools/rtk-query-monitor": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@redux-devtools/rtk-query-monitor/-/rtk-query-monitor-5.0.1.tgz", + "integrity": "sha512-mqYkEop8lgyswW4HxEDnPBsdxU/h3Rm+/q0KCbeuVSZU7Ek/gEgSnKAi++JvsTMPekgA1Uie94/kztOrB6Kv/w==", "dev": true, "dependencies": { - "@babel/runtime": "^7.23.2", - "@redux-devtools/ui": "^1.3.1", - "@types/lodash": "^4.14.201", - "@types/prop-types": "^15.7.10", - "@types/redux-devtools-themes": "^1.0.3", + "@babel/runtime": "^7.24.1", + "@redux-devtools/ui": "^1.3.2", + "@types/lodash": "^4.17.0", "hex-rgba": "^1.0.2", - "immutable": "^4.3.4", - "jss": "^10.10.0", - "jss-preset-default": "^10.10.0", + "immutable": "^4.3.5", "lodash.debounce": "^4.0.8", - "prop-types": "^15.8.1", - "react-base16-styling": "^0.9.1", - "react-json-tree": "^0.18.0", - "redux-devtools-themes": "^1.0.0" + "react-base16-styling": "^0.10.0", + "react-json-tree": "^0.19.0" }, "peerDependencies": { - "@redux-devtools/core": "^3.0.0", + "@emotion/react": "^11.11.4", + "@redux-devtools/core": "^4.0.0", "@reduxjs/toolkit": "^1.0.0 || ^2.0.0", - "@types/react": "^16.3.0 || ^17.0.0 || ^18.0.0", - "@types/styled-components": "^5.0.0", - "react": "^16.3.0 || ^17.0.0 || ^18.0.0", + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "@types/styled-components": "^5.1.34", + "react": "^16.8.4 || ^17.0.0 || ^18.0.0", "redux": "^3.4.0 || ^4.0.0 || ^5.0.0", - "styled-components": "^5.0.0" + "styled-components": "^5.3.11" } }, - "node_modules/@redux-devtools/cli/node_modules/@redux-devtools/slider-monitor": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@redux-devtools/slider-monitor/-/slider-monitor-4.1.0.tgz", - "integrity": "sha512-p0jDgLcmuOQyJTlMAwjSZ8wxioqV802IXtm7BsxGq9021WsmyhEGRhqfu4XvXUt95sBHNsRsm9yE5XVTMmlAtw==", + "node_modules/@redux-devtools/cli/node_modules/@redux-devtools/app/node_modules/@redux-devtools/slider-monitor": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@redux-devtools/slider-monitor/-/slider-monitor-5.0.1.tgz", + "integrity": "sha512-ud04CLxJNMpNfXKrfHtt4i7LbYCi3PVdcrQgU0X3qeEUWiXkvumZJIs0DPKSMk3Ukqy/G06SMNL4aqy6icjkLQ==", "dev": true, "dependencies": { - "@babel/runtime": "^7.23.2", - "@redux-devtools/ui": "^1.3.1", - "@types/prop-types": "^15.7.10", - "@types/redux-devtools-themes": "^1.0.3", - "prop-types": "^15.8.1", - "redux-devtools-themes": "^1.0.0" + "@babel/runtime": "^7.24.1", + "@redux-devtools/ui": "^1.3.2", + "react-base16-styling": "^0.10.0" + }, + "peerDependencies": { + "@redux-devtools/core": "^4.0.0", + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "@types/styled-components": "^5.1.34", + "react": "^16.8.4 || ^17.0.0 || ^18.0.0", + "redux": "^3.4.0 || ^4.0.0 || ^5.0.0", + "styled-components": "^5.3.11" + } + }, + "node_modules/@redux-devtools/cli/node_modules/@redux-devtools/inspector-monitor": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@redux-devtools/inspector-monitor/-/inspector-monitor-6.0.1.tgz", + "integrity": "sha512-FGUOrV3hEKrelqGSWmExKbBfXTEtLQ1yj+gLuPY2yAnZwFwOKzxybL5tsHCvVZsm/hgkA3WGcpYHtZ6+pJSWBg==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.24.1", + "@dnd-kit/core": "^6.1.0", + "@dnd-kit/modifiers": "^7.0.0", + "@dnd-kit/sortable": "^8.0.0", + "@dnd-kit/utilities": "^3.2.2", + "@types/lodash": "^4.17.0", + "dateformat": "^5.0.3", + "hex-rgba": "^1.0.2", + "immutable": "^4.3.5", + "javascript-stringify": "^2.1.0", + "jsondiffpatch": "^0.6.0", + "lodash.debounce": "^4.0.8", + "react-base16-styling": "^0.10.0", + "react-json-tree": "^0.19.0" + }, + "peerDependencies": { + "@emotion/react": "^11.11.4", + "@redux-devtools/core": "^4.0.0", + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.4 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.4 || ^17.0.0 || ^18.0.0", + "redux": "^3.4.0 || ^4.0.0 || ^5.0.0" + } + }, + "node_modules/@redux-devtools/cli/node_modules/@redux-devtools/inspector-monitor/node_modules/jsondiffpatch": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/jsondiffpatch/-/jsondiffpatch-0.6.0.tgz", + "integrity": "sha512-3QItJOXp2AP1uv7waBkao5nCvhEv+QmJAd38Ybq7wNI74Q+BBmnLn4EDKz6yI9xGAIQoUF87qHt+kc1IVxB4zQ==", + "dev": true, + "dependencies": { + "@types/diff-match-patch": "^1.0.36", + "chalk": "^5.3.0", + "diff-match-patch": "^1.0.5" + }, + "bin": { + "jsondiffpatch": "bin/jsondiffpatch.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/@redux-devtools/cli/node_modules/@redux-devtools/ui": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@redux-devtools/ui/-/ui-1.3.2.tgz", + "integrity": "sha512-/g9FSzvOSCkTXibd+h13kRE433/Zv4KVImQaanJsl8fc0MEfBSyFtGE1wjeIkwqs322EVBxw3fPWaQAC1gpLHg==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.24.1", + "@rjsf/core": "^4.2.3", + "@types/codemirror": "^5.60.15", + "@types/json-schema": "^7.0.15", + "@types/simple-element-resize-detector": "^1.3.3", + "codemirror": "^5.65.16", + "color": "^4.2.3", + "react-base16-styling": "^0.10.0", + "react-icons": "^5.0.1", + "react-select": "^5.8.0", + "simple-element-resize-detector": "^1.3.0" }, "peerDependencies": { - "@redux-devtools/core": "^3.0.0", "@types/react": "^16.3.0 || ^17.0.0 || ^18.0.0", - "@types/styled-components": "^5.0.0", + "@types/styled-components": "^5.1.34", "react": "^16.3.0 || ^17.0.0 || ^18.0.0", - "redux": "^3.4.0 || ^4.0.0 || ^5.0.0", - "styled-components": "^5.0.0" + "styled-components": "^5.3.11" + } + }, + "node_modules/@redux-devtools/cli/node_modules/@redux-devtools/ui/node_modules/react-icons": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.2.1.tgz", + "integrity": "sha512-zdbW5GstTzXaVKvGSyTaBalt7HSfuK5ovrzlpyiWHAFXndXTdd/1hdDHI4xBM1Mn7YriT6aqESucFl9kEXzrdw==", + "dev": true, + "peerDependencies": { + "react": "*" } }, "node_modules/@redux-devtools/cli/node_modules/@types/chrome": { - "version": "0.0.251", - "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.251.tgz", - "integrity": "sha512-UF+yr0LEKWWGsKxQ5A3XOSF5SNoU1ctW3pXcWJPpT8OOUTEspYeaLU8spDKe+6xalXeMTS0TBrX1g0b6qlWmkw==", + "version": "0.0.263", + "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.263.tgz", + "integrity": "sha512-As0vzv99ov3M6ZR7R6VzhMWFZXkPMrFrCEXXVrMN576Cm70fTkj7Df2CF+qEo170JepX50pd11cX6O4DSAtl2Q==", "dev": true, "dependencies": { "@types/filesystem": "*", "@types/har-format": "*" } }, + "node_modules/@redux-devtools/cli/node_modules/async-stream-emitter": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/async-stream-emitter/-/async-stream-emitter-4.1.0.tgz", + "integrity": "sha512-cfPZYjHkhCdHSR+eux71vOU8+8Xb23oLyxccAjwYHgOxDb3+qSDb2HV1Y0Hmu39vZlse2cm15CUShLiVYXHCmQ==", + "dev": true, + "dependencies": { + "stream-demux": "^8.1.0" + } + }, "node_modules/@redux-devtools/cli/node_modules/body-parser": { "version": "1.20.2", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", @@ -5383,6 +5422,21 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/@redux-devtools/cli/node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@redux-devtools/cli/node_modules/chalk": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", @@ -5404,6 +5458,16 @@ "node": ">= 0.6" } }, + "node_modules/@redux-devtools/cli/node_modules/d3tooltip": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/d3tooltip/-/d3tooltip-4.0.0.tgz", + "integrity": "sha512-jQRmF04fL9G+sROeN1/CZaHRqE31Pl7Qky6qIJ2MkecTwXZtgfDgmgrPZ0VS5Ewnj9sYWJ6CAl6fYI4PNn2Fpw==", + "dev": true, + "peerDependencies": { + "@types/d3": "^7.4.3", + "d3": "^7.9.0" + } + }, "node_modules/@redux-devtools/cli/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -5413,6 +5477,34 @@ "ms": "2.0.0" } }, + "node_modules/@redux-devtools/cli/node_modules/default-browser": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz", + "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==", + "dev": true, + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@redux-devtools/cli/node_modules/default-browser-id": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz", + "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@redux-devtools/cli/node_modules/define-lazy-prop": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", @@ -5425,12 +5517,36 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@redux-devtools/cli/node_modules/is-wsl": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "dev": true, + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@redux-devtools/cli/node_modules/js-tokens": { "version": "8.0.3", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-8.0.3.tgz", "integrity": "sha512-UfJMcSJc+SEXEl9lH/VLHSZbThQyLpw1vLO1Lb+j4RWDvG3N2f7yj3PVQA3cmkTBNldJ9eFnM+xEXxHIXrYiJw==", "dev": true }, + "node_modules/@redux-devtools/cli/node_modules/map2tree": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/map2tree/-/map2tree-4.0.0.tgz", + "integrity": "sha512-nzoeFAjW5yCwLFpV3A6EZS3kHD6H9y82S0KANc8tN2Mha+Ad2vIggDqxxYdI8lgghHzQL3yf/6tqlcoplRw8NA==", + "dev": true, + "dependencies": { + "lodash-es": "^4.17.21" + } + }, "node_modules/@redux-devtools/cli/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -5438,23 +5554,33 @@ "dev": true }, "node_modules/@redux-devtools/cli/node_modules/open": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/open/-/open-9.1.0.tgz", - "integrity": "sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg==", + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/open/-/open-10.0.2.tgz", + "integrity": "sha512-GnYLdE+E3K8NeSE23N0g67/9q9AXRph5oTUbz6IbIgElPigEnQ2aHuqRge3y0JUr67qoc84xME5kF03fDc3fcA==", "dev": true, "dependencies": { - "default-browser": "^4.0.0", + "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", - "is-wsl": "^2.2.0" + "is-wsl": "^3.1.0" }, "engines": { - "node": ">=14.16" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@redux-devtools/cli/node_modules/ramda": { + "version": "0.29.1", + "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.29.1.tgz", + "integrity": "sha512-OfxIeWzd4xdUNxlWhgFazxsA/nl3mS4/jGZI5n00uWOoSSFRhC1b6gl6xvmzUamgmqELraWp0J/qqVlXYPDPyA==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ramda" + } + }, "node_modules/@redux-devtools/cli/node_modules/raw-body": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", @@ -5471,9 +5597,9 @@ } }, "node_modules/@redux-devtools/cli/node_modules/react-redux": { - "version": "8.1.2", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.1.2.tgz", - "integrity": "sha512-xJKYI189VwfsFc4CJvHqHlDrzyFTY/3vZACbE+rr/zQ34Xx1wQfB4OTOSeOSNrF6BDVe8OOdxIrAnMGXA3ggfw==", + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.1.3.tgz", + "integrity": "sha512-n0ZrutD7DaX/j9VscF+uTALI3oUPa/pO4Z3soOBIjuRn/FzVu6aehhysxZCLi6y7duMf52WNZGMl7CtuK5EnRw==", "dev": true, "dependencies": { "@babel/runtime": "^7.12.1", @@ -5510,11 +5636,23 @@ } }, "node_modules/@redux-devtools/cli/node_modules/regenerator-runtime": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", - "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==", + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", "dev": true }, + "node_modules/@redux-devtools/cli/node_modules/run-applescript": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz", + "integrity": "sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@redux-devtools/cli/node_modules/sc-formatter": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/sc-formatter/-/sc-formatter-4.0.0.tgz", @@ -5565,6 +5703,16 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/@redux-devtools/cli/node_modules/stream-demux": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/stream-demux/-/stream-demux-8.1.0.tgz", + "integrity": "sha512-20vtOmAj2EVzQZKZVmfyio16u/3QOKSvg+0ldgZeS+m2FNI1vKFoqggamagsPCXufdZ1Tk8VvAM/HV/YUmRbSg==", + "dev": true, + "dependencies": { + "consumable-stream": "^2.0.0", + "writable-consumable-stream": "^3.0.1" + } + }, "node_modules/@redux-devtools/cli/node_modules/styled-components": { "version": "5.3.11", "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.11.tgz", @@ -5608,10 +5756,19 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/@redux-devtools/cli/node_modules/writable-consumable-stream": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/writable-consumable-stream/-/writable-consumable-stream-3.0.1.tgz", + "integrity": "sha512-rAOJTA/sMgXD/X6eMwbQJe49w+Fnkdx3iV5oUzdmiZ7Bwx03khqUnAKIpzp/hbI8q2EP5NfjXgIXN0MsipfHeg==", + "dev": true, + "dependencies": { + "consumable-stream": "^2.0.0" + } + }, "node_modules/@redux-devtools/cli/node_modules/ws": { - "version": "8.14.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.1.tgz", - "integrity": "sha512-4OOseMUq8AzRBI/7SLMUwO+FEDnguetSk7KMb1sHwvF2w2Wv5Hoj0nlifx8vtGsftE/jWHojPy8sMMzYLJ2G/A==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", + "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", "dev": true, "engines": { "node": ">=10.0.0" @@ -5724,6 +5881,15 @@ "node": ">=6.9.0" } }, + "node_modules/@redux-devtools/remote/node_modules/async-stream-emitter": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/async-stream-emitter/-/async-stream-emitter-4.1.0.tgz", + "integrity": "sha512-cfPZYjHkhCdHSR+eux71vOU8+8Xb23oLyxccAjwYHgOxDb3+qSDb2HV1Y0Hmu39vZlse2cm15CUShLiVYXHCmQ==", + "dev": true, + "dependencies": { + "stream-demux": "^8.1.0" + } + }, "node_modules/@redux-devtools/remote/node_modules/regenerator-runtime": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", @@ -5756,6 +5922,25 @@ "ws": "^8.9.0" } }, + "node_modules/@redux-devtools/remote/node_modules/stream-demux": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/stream-demux/-/stream-demux-8.1.0.tgz", + "integrity": "sha512-20vtOmAj2EVzQZKZVmfyio16u/3QOKSvg+0ldgZeS+m2FNI1vKFoqggamagsPCXufdZ1Tk8VvAM/HV/YUmRbSg==", + "dev": true, + "dependencies": { + "consumable-stream": "^2.0.0", + "writable-consumable-stream": "^3.0.1" + } + }, + "node_modules/@redux-devtools/remote/node_modules/writable-consumable-stream": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/writable-consumable-stream/-/writable-consumable-stream-3.0.1.tgz", + "integrity": "sha512-rAOJTA/sMgXD/X6eMwbQJe49w+Fnkdx3iV5oUzdmiZ7Bwx03khqUnAKIpzp/hbI8q2EP5NfjXgIXN0MsipfHeg==", + "dev": true, + "dependencies": { + "consumable-stream": "^2.0.0" + } + }, "node_modules/@redux-devtools/remote/node_modules/ws": { "version": "8.16.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", @@ -5808,54 +5993,6 @@ "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", "dev": true }, - "node_modules/@redux-devtools/ui": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@redux-devtools/ui/-/ui-1.3.1.tgz", - "integrity": "sha512-qtfau1zSmv6nSUrnesuxaos1V7r89HQV2Hq/thcRcyHd0Y0OYgpQlnwEK3wkK1esLU2BhEUUb85DC8IdJ3l6Sg==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.23.2", - "@rjsf/core": "^4.2.3", - "@types/base16": "^1.0.5", - "@types/codemirror": "^5.60.13", - "@types/json-schema": "^7.0.15", - "@types/prop-types": "^15.7.10", - "@types/redux-devtools-themes": "^1.0.3", - "@types/simple-element-resize-detector": "^1.3.3", - "base16": "^1.0.0", - "codemirror": "^5.65.15", - "color": "^4.2.3", - "prop-types": "^15.8.1", - "react-icons": "^4.11.0", - "react-select": "^5.8.0", - "redux-devtools-themes": "^1.0.0", - "simple-element-resize-detector": "^1.3.0" - }, - "peerDependencies": { - "@types/react": "^16.3.0 || ^17.0.0 || ^18.0.0", - "@types/styled-components": "^5.0.0", - "react": "^16.3.0 || ^17.0.0 || ^18.0.0", - "styled-components": "^5.0.0" - } - }, - "node_modules/@redux-devtools/ui/node_modules/@babel/runtime": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.9.tgz", - "integrity": "sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==", - "dev": true, - "dependencies": { - "regenerator-runtime": "^0.14.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@redux-devtools/ui/node_modules/regenerator-runtime": { - "version": "0.14.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", - "dev": true - }, "node_modules/@redux-devtools/utils": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@redux-devtools/utils/-/utils-3.0.0.tgz", @@ -6568,12 +6705,6 @@ "@babel/types": "^7.3.0" } }, - "node_modules/@types/base16": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/base16/-/base16-1.0.5.tgz", - "integrity": "sha512-OzOWrTluG9cwqidEzC/Q6FAmIPcnZfm8BFRlIx0+UIUqnuAmi5OS88O0RpT3Yz6qdmqObvUhasrbNsCofE4W9A==", - "dev": true - }, "node_modules/@types/big.js": { "version": "6.1.6", "resolved": "https://registry.npmjs.org/@types/big.js/-/big.js-6.1.6.tgz", @@ -6658,9 +6789,9 @@ } }, "node_modules/@types/d3": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.0.tgz", - "integrity": "sha512-jIfNVK0ZlxcuRDKtRS/SypEyOQ6UHaFQBKv032X45VvxSJ6Yi5G9behy9h6tNTHTDGh5Vq+KbmBjUWLgY4meCA==", + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", + "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", "dev": true, "dependencies": { "@types/d3-array": "*", @@ -6696,45 +6827,45 @@ } }, "node_modules/@types/d3-array": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.0.7.tgz", - "integrity": "sha512-4/Q0FckQ8TBjsB0VdGFemJOG8BLXUB2KKlL0VmZ+eOYeOnTb/wDRQqYWpBmQ6IlvWkXwkYiot+n9Px2aTJ7zGQ==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz", + "integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==", "dev": true }, "node_modules/@types/d3-axis": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.3.tgz", - "integrity": "sha512-SE3x/pLO/+GIHH17mvs1uUVPkZ3bHquGzvZpPAh4yadRy71J93MJBpgK/xY8l9gT28yTN1g9v3HfGSFeBMmwZw==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz", + "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==", "dev": true, "dependencies": { "@types/d3-selection": "*" } }, "node_modules/@types/d3-brush": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.3.tgz", - "integrity": "sha512-MQ1/M/B5ifTScHSe5koNkhxn2mhUPqXjGuKjjVYckplAPjP9t2I2sZafb/YVHDwhoXWZoSav+Q726eIbN3qprA==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz", + "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", "dev": true, "dependencies": { "@types/d3-selection": "*" } }, "node_modules/@types/d3-chord": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.3.tgz", - "integrity": "sha512-keuSRwO02c7PBV3JMWuctIfdeJrVFI7RpzouehvBWL4/GGUB3PBNg/9ZKPZAgJphzmS2v2+7vr7BGDQw1CAulw==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz", + "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==", "dev": true }, "node_modules/@types/d3-color": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.0.tgz", - "integrity": "sha512-HKuicPHJuvPgCD+np6Se9MQvS6OCbJmOjGvylzMJRlDwUXjKTTXs6Pwgk79O09Vj/ho3u1ofXnhFOaEWWPrlwA==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", "dev": true }, "node_modules/@types/d3-contour": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.3.tgz", - "integrity": "sha512-x7G/tdDZt4m09XZnG2SutbIuQqmkNYqR9uhDMdPlpJbcwepkEjEWG29euFcgVA1k6cn92CHdDL9Z+fOnxnbVQw==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz", + "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", "dev": true, "dependencies": { "@types/d3-array": "*", @@ -6742,174 +6873,180 @@ } }, "node_modules/@types/d3-delaunay": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.1.tgz", - "integrity": "sha512-tLxQ2sfT0p6sxdG75c6f/ekqxjyYR0+LwPrsO1mbC9YDBzPJhs2HbJJRrn8Ez1DBoHRo2yx7YEATI+8V1nGMnQ==", + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==", "dev": true }, "node_modules/@types/d3-dispatch": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.3.tgz", - "integrity": "sha512-Df7KW3Re7G6cIpIhQtqHin8yUxUHYAqiE41ffopbmU5+FifYUNV7RVyTg8rQdkEagg83m14QtS8InvNb95Zqug==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.6.tgz", + "integrity": "sha512-4fvZhzMeeuBJYZXRXrRIQnvUYfyXwYmLsdiN7XXmVNQKKw1cM8a5WdID0g1hVFZDqT9ZqZEY5pD44p24VS7iZQ==", "dev": true }, "node_modules/@types/d3-drag": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.3.tgz", - "integrity": "sha512-82AuQMpBQjuXeIX4tjCYfWjpm3g7aGCfx6dFlxX2JlRaiME/QWcHzBsINl7gbHCODA2anPYlL31/Trj/UnjK9A==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", "dev": true, "dependencies": { "@types/d3-selection": "*" } }, "node_modules/@types/d3-dsv": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.2.tgz", - "integrity": "sha512-DooW5AOkj4AGmseVvbwHvwM/Ltu0Ks0WrhG6r5FG9riHT5oUUTHz6xHsHqJSVU8ZmPkOqlUEY2obS5C9oCIi2g==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", + "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==", "dev": true }, "node_modules/@types/d3-ease": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.0.tgz", - "integrity": "sha512-aMo4eaAOijJjA6uU+GIeW018dvy9+oH5Y2VPPzjjfxevvGQ/oRDs+tfYC9b50Q4BygRR8yE2QCLsrT0WtAVseA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", "dev": true }, "node_modules/@types/d3-fetch": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.3.tgz", - "integrity": "sha512-/EsDKRiQkby3Z/8/AiZq8bsuLDo/tYHnNIZkUpSeEHWV7fHUl6QFBjvMPbhkKGk9jZutzfOkGygCV7eR/MkcXA==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", + "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", "dev": true, "dependencies": { "@types/d3-dsv": "*" } }, "node_modules/@types/d3-force": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.5.tgz", - "integrity": "sha512-EGG+IWx93ESSXBwfh/5uPuR9Hp8M6o6qEGU7bBQslxCvrdUBQZha/EFpu/VMdLU4B0y4Oe4h175nSm7p9uqFug==", + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.9.tgz", + "integrity": "sha512-IKtvyFdb4Q0LWna6ymywQsEYjK/94SGhPrMfEr1TIc5OBeziTi+1jcCvttts8e0UWZIxpasjnQk9MNk/3iS+kA==", "dev": true }, "node_modules/@types/d3-format": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.1.tgz", - "integrity": "sha512-5KY70ifCCzorkLuIkDe0Z9YTf9RR2CjBX1iaJG+rgM/cPP+sO+q9YdQ9WdhQcgPj1EQiJ2/0+yUkkziTG6Lubg==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", + "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==", "dev": true }, "node_modules/@types/d3-geo": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.0.4.tgz", - "integrity": "sha512-kmUK8rVVIBPKJ1/v36bk2aSgwRj2N/ZkjDT+FkMT5pgedZoPlyhaG62J+9EgNIgUXE6IIL0b7bkLxCzhE6U4VQ==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", "dev": true, "dependencies": { "@types/geojson": "*" } }, "node_modules/@types/d3-hierarchy": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.3.tgz", - "integrity": "sha512-GpSK308Xj+HeLvogfEc7QsCOcIxkDwLhFYnOoohosEzOqv7/agxwvJER1v/kTC+CY1nfazR0F7gnHo7GE41/fw==", + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.6.tgz", + "integrity": "sha512-qlmD/8aMk5xGorUvTUWHCiumvgaUXYldYjNVOWtYoTYY/L+WwIEAmJxUmTgr9LoGNG0PPAOmqMDJVDPc7DOpPw==", "dev": true }, "node_modules/@types/d3-interpolate": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.1.tgz", - "integrity": "sha512-jx5leotSeac3jr0RePOH1KdR9rISG91QIE4Q2PYTu4OymLTZfA3SrnURSLzKH48HmXVUru50b8nje4E79oQSQw==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", "dev": true, "dependencies": { "@types/d3-color": "*" } }, "node_modules/@types/d3-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.0.0.tgz", - "integrity": "sha512-0g/A+mZXgFkQxN3HniRDbXMN79K3CdTpLsevj+PXiTcb2hVyvkZUBg37StmgCQkaD84cUJ4uaDAWq7UJOQy2Tg==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.0.2.tgz", + "integrity": "sha512-WAIEVlOCdd/NKRYTsqCpOMHQHemKBEINf8YXMYOtXH0GA7SY0dqMB78P3Uhgfy+4X+/Mlw2wDtlETkN6kQUCMA==", "dev": true }, "node_modules/@types/d3-polygon": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.0.tgz", - "integrity": "sha512-D49z4DyzTKXM0sGKVqiTDTYr+DHg/uxsiWDAkNrwXYuiZVd9o9wXZIo+YsHkifOiyBkmSWlEngHCQme54/hnHw==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", + "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==", "dev": true }, "node_modules/@types/d3-quadtree": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.2.tgz", - "integrity": "sha512-QNcK8Jguvc8lU+4OfeNx+qnVy7c0VrDJ+CCVFS9srBo2GL9Y18CnIxBdTF3v38flrGy5s1YggcoAiu6s4fLQIw==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", + "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==", "dev": true }, "node_modules/@types/d3-random": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.1.tgz", - "integrity": "sha512-IIE6YTekGczpLYo/HehAy3JGF1ty7+usI97LqraNa8IiDur+L44d0VOjAvFQWJVdZOJHukUJw+ZdZBlgeUsHOQ==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz", + "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==", "dev": true }, "node_modules/@types/d3-scale": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.4.tgz", - "integrity": "sha512-eq1ZeTj0yr72L8MQk6N6heP603ubnywSDRfNpi5enouR112HzGLS6RIvExCzZTraFF4HdzNpJMwA/zGiMoHUUw==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.8.tgz", + "integrity": "sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==", "dev": true, "dependencies": { "@types/d3-time": "*" } }, "node_modules/@types/d3-scale-chromatic": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz", - "integrity": "sha512-dsoJGEIShosKVRBZB0Vo3C8nqSDqVGujJU6tPznsBJxNJNwMF8utmS83nvCBKQYPpjCzaaHcrf66iTRpZosLPw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.0.3.tgz", + "integrity": "sha512-laXM4+1o5ImZv3RpFAsTRn3TEkzqkytiOY0Dz0sq5cnd1dtNlk6sHLon4OvqaiJb28T0S/TdsBI3Sjsy+keJrw==", "dev": true }, "node_modules/@types/d3-selection": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.6.tgz", - "integrity": "sha512-2ACr96USZVjXR9KMD9IWi1Epo4rSDKnUtYn6q2SPhYxykvXTw9vR77lkFNruXVg4i1tzQtBxeDMx0oNvJWbF1w==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.10.tgz", + "integrity": "sha512-cuHoUgS/V3hLdjJOLTT691+G2QoqAjCVLmr4kJXR4ha56w1Zdu8UUQ5TxLRqudgNjwXeQxKMq4j+lyf9sWuslg==", "dev": true }, "node_modules/@types/d3-shape": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.2.tgz", - "integrity": "sha512-NN4CXr3qeOUNyK5WasVUV8NCSAx/CRVcwcb0BuuS1PiTqwIm6ABi1SyasLZ/vsVCFDArF+W4QiGzSry1eKYQ7w==", + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.6.tgz", + "integrity": "sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA==", "dev": true, "dependencies": { "@types/d3-path": "*" } }, "node_modules/@types/d3-time": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.0.tgz", - "integrity": "sha512-sZLCdHvBUcNby1cB6Fd3ZBrABbjz3v1Vm90nysCQ6Vt7vd6e/h9Lt7SiJUoEX0l4Dzc7P5llKyhqSi1ycSf1Hg==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.3.tgz", + "integrity": "sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw==", "dev": true }, "node_modules/@types/d3-time-format": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.0.tgz", - "integrity": "sha512-yjfBUe6DJBsDin2BMIulhSHmr5qNR5Pxs17+oW4DoVPyVIXZ+m6bs7j1UVKP08Emv6jRmYrYqxYzO63mQxy1rw==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", + "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==", "dev": true }, "node_modules/@types/d3-timer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.0.tgz", - "integrity": "sha512-HNB/9GHqu7Fo8AQiugyJbv6ZxYz58wef0esl4Mv828w1ZKpAshw/uFWVDUcIB9KKFeFKoxS3cHY07FFgtTRZ1g==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", "dev": true }, "node_modules/@types/d3-transition": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.4.tgz", - "integrity": "sha512-512a4uCOjUzsebydItSXsHrPeQblCVk8IKjqCUmrlvBWkkVh3donTTxmURDo1YPwIVDh5YVwCAO6gR4sgimCPQ==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.8.tgz", + "integrity": "sha512-ew63aJfQ/ms7QQ4X7pk5NxQ9fZH/z+i24ZfJ6tJSfqxJMrYLiK01EAs2/Rtw/JreGUsS3pLPNV644qXFGnoZNQ==", "dev": true, "dependencies": { "@types/d3-selection": "*" } }, "node_modules/@types/d3-zoom": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.4.tgz", - "integrity": "sha512-cqkuY1ah9ZQre2POqjSLcM8g40UVya/qwEUrNYP2/rCVljbmqKCVcv+ebvwhlI5azIbSEL7m+os6n+WlYA43aA==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", + "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", "dev": true, "dependencies": { "@types/d3-interpolate": "*", "@types/d3-selection": "*" } }, + "node_modules/@types/diff-match-patch": { + "version": "1.0.36", + "resolved": "https://registry.npmjs.org/@types/diff-match-patch/-/diff-match-patch-1.0.36.tgz", + "integrity": "sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg==", + "dev": true + }, "node_modules/@types/elliptic": { "version": "6.4.14", "resolved": "https://registry.npmjs.org/@types/elliptic/-/elliptic-6.4.14.tgz", @@ -6994,9 +7131,9 @@ "dev": true }, "node_modules/@types/geojson": { - "version": "7946.0.10", - "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.10.tgz", - "integrity": "sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA==", + "version": "7946.0.13", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.13.tgz", + "integrity": "sha512-bmrNrgKMOhM3WsafmbGmC+6dsF2Z308vLFsQ3a/bT8X8Sv5clVYpPars/UPq+sAaJP+5OoLAYgwbkS5QEJdLUQ==", "dev": true }, "node_modules/@types/get-params": { @@ -7151,9 +7288,9 @@ } }, "node_modules/@types/lodash": { - "version": "4.14.202", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.202.tgz", - "integrity": "sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==" + "version": "4.17.4", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.4.tgz", + "integrity": "sha512-wYCP26ZLxaT3R39kiN2+HcJ4kTd3U1waI/cY7ivWYqFP6pW3ZNpvi6Wd6PHZx7T/t8z0vlkXMg3QYLa7DZ/IJQ==" }, "node_modules/@types/lodash.debounce": { "version": "4.0.9", @@ -7205,27 +7342,13 @@ } }, "node_modules/@types/node-fetch": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.4.tgz", - "integrity": "sha512-1ZX9fcN4Rvkvgv4E6PAY5WXUFWFcRWxZa3EW83UjycOB9ljJCedb2CupIP4RZMEwF/M3eTcCihbBRgwtGbg5Rg==", + "version": "2.6.10", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.10.tgz", + "integrity": "sha512-PPpPK6F9ALFTn59Ka3BaL+qGuipRfxNE8qVgkp0bVixeiR2c2/L+IVOiBdu9JhhT22sWnQEp6YyHGI2b2+CMcA==", "dev": true, "dependencies": { "@types/node": "*", - "form-data": "^3.0.0" - } - }, - "node_modules/@types/node-fetch/node_modules/form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", - "dev": true, - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" + "form-data": "^4.0.0" } }, "node_modules/@types/parse-json": { @@ -7285,19 +7408,10 @@ "@types/react": "*" } }, - "node_modules/@types/redux-devtools-themes": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@types/redux-devtools-themes/-/redux-devtools-themes-1.0.3.tgz", - "integrity": "sha512-KqiQ2+6VTb1Yn02+ZNQsB0XcoJTu/W4AhnIUaeTnkVFie5YWMoeSpW4IOFYJ2/HJ+wi1kLw+PHDgjZ3t+M6IRw==", - "dev": true, - "dependencies": { - "@types/base16": "*" - } - }, "node_modules/@types/responselike": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", - "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", + "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", "dev": true, "dependencies": { "@types/node": "*" @@ -7365,9 +7479,9 @@ "dev": true }, "node_modules/@types/styled-components": { - "version": "5.1.26", - "resolved": "https://registry.npmjs.org/@types/styled-components/-/styled-components-5.1.26.tgz", - "integrity": "sha512-KuKJ9Z6xb93uJiIyxo/+ksS7yLjS1KzG6iv5i78dhVg/X3u5t1H7juRWqVmodIdz6wGVaIApo1u01kmFRdJHVw==", + "version": "5.1.34", + "resolved": "https://registry.npmjs.org/@types/styled-components/-/styled-components-5.1.34.tgz", + "integrity": "sha512-mmiVvwpYklFIv9E8qfxuPyIt/OuyIrn6gMOAMOFUO3WJfSrSE+sGUoa4PiZj77Ut7bKZpaa6o1fBKS/4TOEvnA==", "dev": true, "dependencies": { "@types/hoist-non-react-statics": "*", @@ -8629,13 +8743,13 @@ } }, "node_modules/ag-auth": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ag-auth/-/ag-auth-2.0.0.tgz", - "integrity": "sha512-gk5BgWzijPUaCMDYFtuggIlUDWGMlMSOdF/tdQa5sM7Nh0KowohFh3P3sx06gegufKWzWF97dwP/7hLg3CkqWg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ag-auth/-/ag-auth-2.0.1.tgz", + "integrity": "sha512-PpIBdQiTIzp+ELJ8pPHVvxxCIUGXONPSecFgh34zyr/Z3vpqPmT/S21u6lzt1jGPulOTic4Wm9FpVGzC/BYHSQ==", "dev": true, "dependencies": { "jsonwebtoken": "^9.0.0", - "sc-errors": "^2.0.0" + "sc-errors": "^2.0.2" } }, "node_modules/ag-channel": { @@ -8648,23 +8762,23 @@ } }, "node_modules/ag-request": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/ag-request/-/ag-request-1.0.0.tgz", - "integrity": "sha512-2f7I0cQLMVyGAqjSewVMEFuAsJsIY6egdE16UHS636r+8c6Oevrv0j6SrOIXyRN6yuNT4PBuhiKmrhHbh9OvEg==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ag-request/-/ag-request-1.0.1.tgz", + "integrity": "sha512-3F4pDpLy9mxOXop7LoWE78J5g2jmiEJ0gJfzcECOsf/NaCfyeNmOdNLDVM5dS4Hvbi9T+HENL4DmXq5XSotPaA==", "dev": true, "dependencies": { - "sc-errors": "^2.0.0" + "sc-errors": "^2.0.2" } }, "node_modules/ag-simple-broker": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ag-simple-broker/-/ag-simple-broker-5.0.0.tgz", - "integrity": "sha512-LMwfBdoNm+8ac/RkgW6z1mjIvy2cgEqWa9cJUD5sc5uiJkIn/kXhVRlPdfa/MJtxPivo9DRhKb9DlSp2gCv+Cg==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ag-simple-broker/-/ag-simple-broker-6.0.1.tgz", + "integrity": "sha512-pDlHotEoC9uV2Uk8DrR570QXMiUd9QYwJZXWDlBJZEbYTHzMJLEJDJStxmn7Kp4eT7SIGoPFuzELYZyMYNZ2Kw==", "dev": true, "dependencies": { "ag-channel": "^5.0.0", - "async-stream-emitter": "^4.0.0", - "stream-demux": "^8.0.0" + "async-stream-emitter": "^7.0.1", + "stream-demux": "^10.0.1" } }, "node_modules/agent-base": { @@ -9144,12 +9258,12 @@ } }, "node_modules/async-stream-emitter": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/async-stream-emitter/-/async-stream-emitter-4.1.0.tgz", - "integrity": "sha512-cfPZYjHkhCdHSR+eux71vOU8+8Xb23oLyxccAjwYHgOxDb3+qSDb2HV1Y0Hmu39vZlse2cm15CUShLiVYXHCmQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/async-stream-emitter/-/async-stream-emitter-7.0.1.tgz", + "integrity": "sha512-1bgA3iZ80rCBX2LocvsyZPy0QB3/xM+CsXBze2HDHLmshOqx2JlAANGq23djaJ48e9fpcKzTzS1QM0hAKKI0UQ==", "dev": true, "dependencies": { - "stream-demux": "^8.1.0" + "stream-demux": "^10.0.1" } }, "node_modules/asynciterator.prototype": { @@ -9533,12 +9647,6 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, - "node_modules/base16": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/base16/-/base16-1.0.0.tgz", - "integrity": "sha512-pNdYkNPiJUnEhnfXV56+sQy8+AaPcG3POZAUnwr4EeqCUZFz4u2PePbo3e5Gj4ziYPCWGUZT9RHisvJKnwFuBQ==", - "dev": true - }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -9879,12 +9987,12 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -10922,12 +11030,15 @@ } }, "node_modules/clone-response": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", - "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", + "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", "dev": true, "dependencies": { "mimic-response": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/clone-stats": { @@ -11764,16 +11875,6 @@ "postcss-value-parser": "^4.0.2" } }, - "node_modules/css-vendor": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-2.0.8.tgz", - "integrity": "sha512-x9Aq0XTInxrkuFeHKbYC7zWY8ai7qJ04Kxd9MnvbC1uO5DagxoHQjm4JvG+vCdXOoFtCjbL2XSZfxmoYa9uQVQ==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.8.3", - "is-in-browser": "^1.0.2" - } - }, "node_modules/css-what": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", @@ -11828,14 +11929,14 @@ "dev": true }, "node_modules/csstype": { - "version": "3.0.11", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.11.tgz", - "integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==" + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, "node_modules/d3": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/d3/-/d3-7.8.5.tgz", - "integrity": "sha512-JgoahDG51ncUfJu6wX/1vWQEqOflgXyl4MaHqlcSruTez7yhaRKR9i8VjjcQGeS2en/jnFivXuaIMnseMMt0XA==", + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", + "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", "dev": true, "dependencies": { "d3-array": "3", @@ -12270,18 +12371,36 @@ } }, "node_modules/d3tooltip": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3tooltip/-/d3tooltip-3.0.0.tgz", - "integrity": "sha512-bQSxCU0D99KEvpHZjB5oiXToc98FxIHOAAOJRncZMsU3iUU18a2X//FgPnuI0K7wVjfKfgr4dXw5sEvTXSyu6Q==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3tooltip/-/d3tooltip-3.0.1.tgz", + "integrity": "sha512-y1CqKBZE/PUVvXzPeJrAxRh5pj6hzz8/oFdEpgGI0TbhFVhQ1xBv0wCFEz6BzZ94t+5Y+n1x+Q6rGMDtlzy7rA==", "dev": true, "dependencies": { - "@babel/runtime": "^7.20.6" + "@babel/runtime": "^7.23.2" }, "peerDependencies": { - "@types/d3": "^7.4.0", - "d3": "^7.8.0" + "@types/d3": "^7.0.0", + "d3": "^7.0.0" + } + }, + "node_modules/d3tooltip/node_modules/@babel/runtime": { + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.7.tgz", + "integrity": "sha512-w06OXVOFso7LcbzMiDGt+3X7Rh7Ho8MmgPoWU3rarH+8upf+wSU/grlGbWzQyr3DkdN6ZeuMFjpdwW0Q+HxobA==", + "dev": true, + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" } }, + "node_modules/d3tooltip/node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "dev": true + }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -13079,9 +13198,9 @@ "dev": true }, "node_modules/electron": { - "version": "25.9.0", - "resolved": "https://registry.npmjs.org/electron/-/electron-25.9.0.tgz", - "integrity": "sha512-wgscxf2ORHL/8mAQfy7l9rVDG//wrG9RUQndG508kCCMHRq9deFyZ4psOMzySheBRSfGMcFoRFYSlkAeZr8cFg==", + "version": "27.2.0", + "resolved": "https://registry.npmjs.org/electron/-/electron-27.2.0.tgz", + "integrity": "sha512-no/iMICVLI/5G0IqgKFbB89HDN88DWwKeRO+dPfJPkpJISdEX8Cx/sMEOFuuRa4VNInNe5CKCqRWExK5z3AdcQ==", "dev": true, "hasInstallScript": true, "dependencies": { @@ -13102,9 +13221,9 @@ "integrity": "sha512-/Ng/W/kFv7wdEHYzxdK7Cv0BHEGSkSB3M0Ssl8Ndr1eMiYeas/+Mv4cNaDqamqWx6nd2uQZfPz6g25z25M/sdw==" }, "node_modules/electron/node_modules/@types/node": { - "version": "18.18.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.9.tgz", - "integrity": "sha512-0f5klcuImLnG4Qreu9hPj/rEfFq6YRc5n2mAjSsH+ec/mJL+3voBH0+8T7o8RpFjH7ovc+TRsL/c7OYIQsPTfQ==", + "version": "18.19.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.4.tgz", + "integrity": "sha512-xNzlUhzoHotIsnFoXmJB+yWmBvFZgKCI9TtPIEdYIMM1KWfwuY8zh7wvc1u1OAXlC7dlf6mZVx/s+Y5KfFz19A==", "dev": true, "dependencies": { "undici-types": "~5.26.4" @@ -13697,9 +13816,9 @@ } }, "node_modules/eslint-config-prettier": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz", - "integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", + "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", "dev": true, "bin": { "eslint-config-prettier": "bin/cli.js" @@ -14805,9 +14924,9 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "dependencies": { "to-regex-range": "^5.0.1" @@ -15879,9 +15998,9 @@ "dev": true }, "node_modules/graphql": { - "version": "16.8.0", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.8.0.tgz", - "integrity": "sha512-0oKGaR+y3qcS5mCu1vb7KG+a89vjn06C7Ihq/dDl3jA+A8B3TKomvi3CiEcVLJQGalbu8F52LxkOym7U5sSfbg==", + "version": "16.8.1", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.8.1.tgz", + "integrity": "sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw==", "dev": true, "engines": { "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" @@ -16197,9 +16316,9 @@ } }, "node_modules/html-entities": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.4.0.tgz", - "integrity": "sha512-igBTJcNNNhvZFRtm8uA6xMY6xYleeDwn3PeBCkDz7tHttv4F2hsDI2aPgNERWzvRcNYHNT3ymRaQzllmXj4YsQ==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.5.2.tgz", + "integrity": "sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==", "dev": true, "funding": [ { @@ -16331,9 +16450,9 @@ } }, "node_modules/html-webpack-plugin": { - "version": "5.5.3", - "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.5.3.tgz", - "integrity": "sha512-6YrDKTuqaP/TquFH7h4srYWsZx+x6k6+FbsTm0ziCwGHDP78Unr1r9F/H4+sGmMbX08GQcJ+K64x55b+7VM/jg==", + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.0.tgz", + "integrity": "sha512-iwaY4wzbe48AfKLZ/Cc8k0L+FKG6oSNRaZ8x5A/T/IVDGyXcbHncM9TdDa93wn0FsSm82FhTKW7f3vS61thXAw==", "dev": true, "dependencies": { "@types/html-minifier-terser": "^6.0.0", @@ -16350,7 +16469,16 @@ "url": "https://opencollective.com/html-webpack-plugin" }, "peerDependencies": { + "@rspack/core": "0.x || 1.x", "webpack": "^5.20.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } } }, "node_modules/htmlparser2": { @@ -16551,12 +16679,6 @@ "url": "https://github.com/sponsors/typicode" } }, - "node_modules/hyphenate-style-name": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz", - "integrity": "sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==", - "dev": true - }, "node_modules/i18next": { "version": "23.11.0", "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.11.0.tgz", @@ -16844,9 +16966,9 @@ } }, "node_modules/immutable": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.4.tgz", - "integrity": "sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA==", + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.6.tgz", + "integrity": "sha512-Ju0+lEMyzMVZarkTn/gqRpdqd5dOPaz1mCZ0SH3JV6iFw81PldE/PEB1hWVEA288HPt4WXW8O7AWxB10M+03QQ==", "dev": true }, "node_modules/import-fresh": { @@ -17260,12 +17382,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-in-browser": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz", - "integrity": "sha512-FeXIBgG/CPGd/WUxuEyvgGTEfwiG9Z4EKGxjNMRqviiIIfsmgrpnHLffEDdwUHqNva1VEW91o3xBT/m8Elgl9g==", - "dev": true - }, "node_modules/is-inside-container": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", @@ -20114,172 +20230,6 @@ "node": ">=0.6.0" } }, - "node_modules/jss": { - "version": "10.10.0", - "resolved": "https://registry.npmjs.org/jss/-/jss-10.10.0.tgz", - "integrity": "sha512-cqsOTS7jqPsPMjtKYDUpdFC0AbhYFLTcuGRqymgmdJIeQ8cH7+AgX7YSgQy79wXloZq2VvATYxUOUQEvS1V/Zw==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.3.1", - "csstype": "^3.0.2", - "is-in-browser": "^1.1.3", - "tiny-warning": "^1.0.2" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/jss" - } - }, - "node_modules/jss-plugin-camel-case": { - "version": "10.10.0", - "resolved": "https://registry.npmjs.org/jss-plugin-camel-case/-/jss-plugin-camel-case-10.10.0.tgz", - "integrity": "sha512-z+HETfj5IYgFxh1wJnUAU8jByI48ED+v0fuTuhKrPR+pRBYS2EDwbusU8aFOpCdYhtRc9zhN+PJ7iNE8pAWyPw==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.3.1", - "hyphenate-style-name": "^1.0.3", - "jss": "10.10.0" - } - }, - "node_modules/jss-plugin-compose": { - "version": "10.10.0", - "resolved": "https://registry.npmjs.org/jss-plugin-compose/-/jss-plugin-compose-10.10.0.tgz", - "integrity": "sha512-F5kgtWpI2XfZ3Z8eP78tZEYFdgTIbpA/TMuX3a8vwrNolYtN1N4qJR/Ob0LAsqIwCMLojtxN7c7Oo/+Vz6THow==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.3.1", - "jss": "10.10.0", - "tiny-warning": "^1.0.2" - } - }, - "node_modules/jss-plugin-default-unit": { - "version": "10.10.0", - "resolved": "https://registry.npmjs.org/jss-plugin-default-unit/-/jss-plugin-default-unit-10.10.0.tgz", - "integrity": "sha512-SvpajxIECi4JDUbGLefvNckmI+c2VWmP43qnEy/0eiwzRUsafg5DVSIWSzZe4d2vFX1u9nRDP46WCFV/PXVBGQ==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.3.1", - "jss": "10.10.0" - } - }, - "node_modules/jss-plugin-expand": { - "version": "10.10.0", - "resolved": "https://registry.npmjs.org/jss-plugin-expand/-/jss-plugin-expand-10.10.0.tgz", - "integrity": "sha512-ymT62W2OyDxBxr7A6JR87vVX9vTq2ep5jZLIdUSusfBIEENLdkkc0lL/Xaq8W9s3opUq7R0sZQpzRWELrfVYzA==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.3.1", - "jss": "10.10.0" - } - }, - "node_modules/jss-plugin-extend": { - "version": "10.10.0", - "resolved": "https://registry.npmjs.org/jss-plugin-extend/-/jss-plugin-extend-10.10.0.tgz", - "integrity": "sha512-sKYrcMfr4xxigmIwqTjxNcHwXJIfvhvjTNxF+Tbc1NmNdyspGW47Ey6sGH8BcQ4FFQhLXctpWCQSpDwdNmXSwg==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.3.1", - "jss": "10.10.0", - "tiny-warning": "^1.0.2" - } - }, - "node_modules/jss-plugin-global": { - "version": "10.10.0", - "resolved": "https://registry.npmjs.org/jss-plugin-global/-/jss-plugin-global-10.10.0.tgz", - "integrity": "sha512-icXEYbMufiNuWfuazLeN+BNJO16Ge88OcXU5ZDC2vLqElmMybA31Wi7lZ3lf+vgufRocvPj8443irhYRgWxP+A==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.3.1", - "jss": "10.10.0" - } - }, - "node_modules/jss-plugin-nested": { - "version": "10.10.0", - "resolved": "https://registry.npmjs.org/jss-plugin-nested/-/jss-plugin-nested-10.10.0.tgz", - "integrity": "sha512-9R4JHxxGgiZhurDo3q7LdIiDEgtA1bTGzAbhSPyIOWb7ZubrjQe8acwhEQ6OEKydzpl8XHMtTnEwHXCARLYqYA==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.3.1", - "jss": "10.10.0", - "tiny-warning": "^1.0.2" - } - }, - "node_modules/jss-plugin-props-sort": { - "version": "10.10.0", - "resolved": "https://registry.npmjs.org/jss-plugin-props-sort/-/jss-plugin-props-sort-10.10.0.tgz", - "integrity": "sha512-5VNJvQJbnq/vRfje6uZLe/FyaOpzP/IH1LP+0fr88QamVrGJa0hpRRyAa0ea4U/3LcorJfBFVyC4yN2QC73lJg==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.3.1", - "jss": "10.10.0" - } - }, - "node_modules/jss-plugin-rule-value-function": { - "version": "10.10.0", - "resolved": "https://registry.npmjs.org/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.10.0.tgz", - "integrity": "sha512-uEFJFgaCtkXeIPgki8ICw3Y7VMkL9GEan6SqmT9tqpwM+/t+hxfMUdU4wQ0MtOiMNWhwnckBV0IebrKcZM9C0g==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.3.1", - "jss": "10.10.0", - "tiny-warning": "^1.0.2" - } - }, - "node_modules/jss-plugin-rule-value-observable": { - "version": "10.10.0", - "resolved": "https://registry.npmjs.org/jss-plugin-rule-value-observable/-/jss-plugin-rule-value-observable-10.10.0.tgz", - "integrity": "sha512-ZLMaYrR3QE+vD7nl3oNXuj79VZl9Kp8/u6A1IbTPDcuOu8b56cFdWRZNZ0vNr8jHewooEeq2doy8Oxtymr2ZPA==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.3.1", - "jss": "10.10.0", - "symbol-observable": "^1.2.0" - } - }, - "node_modules/jss-plugin-template": { - "version": "10.10.0", - "resolved": "https://registry.npmjs.org/jss-plugin-template/-/jss-plugin-template-10.10.0.tgz", - "integrity": "sha512-ocXZBIOJOA+jISPdsgkTs8wwpK6UbsvtZK5JI7VUggTD6LWKbtoxUzadd2TpfF+lEtlhUmMsCkTRNkITdPKa6w==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.3.1", - "jss": "10.10.0", - "tiny-warning": "^1.0.2" - } - }, - "node_modules/jss-plugin-vendor-prefixer": { - "version": "10.10.0", - "resolved": "https://registry.npmjs.org/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.10.0.tgz", - "integrity": "sha512-UY/41WumgjW8r1qMCO8l1ARg7NHnfRVWRhZ2E2m0DMYsr2DD91qIXLyNhiX83hHswR7Wm4D+oDYNC1zWCJWtqg==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.3.1", - "css-vendor": "^2.0.8", - "jss": "10.10.0" - } - }, - "node_modules/jss-preset-default": { - "version": "10.10.0", - "resolved": "https://registry.npmjs.org/jss-preset-default/-/jss-preset-default-10.10.0.tgz", - "integrity": "sha512-GL175Wt2FGhjE+f+Y3aWh+JioL06/QWFgZp53CbNNq6ZkVU0TDplD8Bxm9KnkotAYn3FlplNqoW5CjyLXcoJ7Q==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.3.1", - "jss": "10.10.0", - "jss-plugin-camel-case": "10.10.0", - "jss-plugin-compose": "10.10.0", - "jss-plugin-default-unit": "10.10.0", - "jss-plugin-expand": "10.10.0", - "jss-plugin-extend": "10.10.0", - "jss-plugin-global": "10.10.0", - "jss-plugin-nested": "10.10.0", - "jss-plugin-props-sort": "10.10.0", - "jss-plugin-rule-value-function": "10.10.0", - "jss-plugin-rule-value-observable": "10.10.0", - "jss-plugin-template": "10.10.0", - "jss-plugin-vendor-prefixer": "10.10.0" - } - }, "node_modules/jsx-ast-utils": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz", @@ -20398,9 +20348,9 @@ } }, "node_modules/knex": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/knex/-/knex-2.5.1.tgz", - "integrity": "sha512-z78DgGKUr4SE/6cm7ku+jHvFT0X97aERh/f0MUKAKgFnwCYBEW4TFBqtHWFYiJFid7fMrtpZ/gxJthvz5mEByA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/knex/-/knex-3.1.0.tgz", + "integrity": "sha512-GLoII6hR0c4ti243gMs5/1Rb3B+AjwMOfjYm97pu0FOQa7JH56hgBxYf5WK2525ceSbBY1cjeZ9yk99GPMB6Kw==", "dev": true, "dependencies": { "colorette": "2.0.19", @@ -20412,7 +20362,7 @@ "getopts": "2.3.0", "interpret": "^2.2.0", "lodash": "^4.17.21", - "pg-connection-string": "2.6.1", + "pg-connection-string": "2.6.2", "rechoir": "^0.8.0", "resolve-from": "^5.0.0", "tarn": "^3.0.2", @@ -20422,7 +20372,7 @@ "knex": "bin/cli.js" }, "engines": { - "node": ">=12" + "node": ">=16" }, "peerDependenciesMeta": { "better-sqlite3": { @@ -20975,12 +20925,6 @@ "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" }, - "node_modules/lodash.curry": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.curry/-/lodash.curry-4.1.1.tgz", - "integrity": "sha512-/u14pXGviLaweY5JI0IUzgzF2J6Ne8INyzAZjImcryjgkZ+ebruBxy2/JaOOkTqScddcYtakjhSaeemV8lR0tA==", - "dev": true - }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -23298,9 +23242,9 @@ "dev": true }, "node_modules/pg-connection-string": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.1.tgz", - "integrity": "sha512-w6ZzNu6oMmIzEAYVw+RLK0+nqHPt8K3ZnknKi+g48Ak2pr3dtljJW3o+D/n2zzCG07Zoe9VOX3aiKpj+BN0pjg==", + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.2.tgz", + "integrity": "sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA==", "dev": true }, "node_modules/picocolors": { @@ -24084,9 +24028,9 @@ } }, "node_modules/react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "dependencies": { "loose-envify": "^1.1.0" }, @@ -24095,28 +24039,15 @@ } }, "node_modules/react-base16-styling": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/react-base16-styling/-/react-base16-styling-0.9.1.tgz", - "integrity": "sha512-1s0CY1zRBOQ5M3T61wetEpvQmsYSNtWEcdYzyZNxKa8t7oDvaOn9d21xrGezGAHFWLM7SHcktPuPTrvoqxSfKw==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.16.7", - "@types/base16": "^1.0.2", - "@types/lodash": "^4.14.178", - "base16": "^1.0.0", - "color": "^3.2.1", - "csstype": "^3.0.10", - "lodash.curry": "^4.1.1" - } - }, - "node_modules/react-base16-styling/node_modules/color": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", - "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/react-base16-styling/-/react-base16-styling-0.10.0.tgz", + "integrity": "sha512-H1k2eFB6M45OaiRru3PBXkuCcn2qNmx+gzLb4a9IPMR7tMH8oBRXU5jGbPDYG1Hz+82d88ED0vjR8BmqU3pQdg==", "dev": true, "dependencies": { - "color-convert": "^1.9.3", - "color-string": "^1.6.0" + "@types/lodash": "^4.17.0", + "color": "^4.2.3", + "csstype": "^3.1.3", + "lodash-es": "^4.17.21" } }, "node_modules/react-dom": { @@ -24205,9 +24136,9 @@ "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" }, "node_modules/react-icons": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.11.0.tgz", - "integrity": "sha512-V+4khzYcE5EBk/BvcuYRq6V/osf11ODUM2J8hg2FDSswRrGvqiYUYPRy4OdrWaQOBj4NcpJfmHZLNaD+VH0TyA==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.12.0.tgz", + "integrity": "sha512-IBaDuHiShdZqmfc/TwHu6+d6k2ltNCf3AszxNmjJc1KUfXdEeRJOKyNvLmAHaarhzGmTSVygNdyu8/opXv2gaw==", "dev": true, "peerDependencies": { "react": "*" @@ -24253,14 +24184,13 @@ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" }, "node_modules/react-json-tree": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/react-json-tree/-/react-json-tree-0.18.0.tgz", - "integrity": "sha512-Qe6HKSXrr++n9Y31nkRJ3XvQMATISpqigH1vEKhLwB56+nk5thTP0ITThpjxY6ZG/ubpVq/aEHIcyLP/OPHxeA==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/react-json-tree/-/react-json-tree-0.19.0.tgz", + "integrity": "sha512-PqT1WRVcWP+RROsZPQfNEKIC1iM/ZMfY4g5jN6oDnXp5593PPRAYgoHcgYCDjflAHQMtxl8XGdlTwIBdEGUXvw==", "dev": true, "dependencies": { - "@babel/runtime": "^7.20.6", - "@types/lodash": "^4.14.191", - "react-base16-styling": "^0.9.1" + "@types/lodash": "^4.17.0", + "react-base16-styling": "^0.10.0" }, "peerDependencies": { "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", @@ -24493,15 +24423,6 @@ "@babel/runtime": "^7.9.2" } }, - "node_modules/redux-devtools-themes": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/redux-devtools-themes/-/redux-devtools-themes-1.0.0.tgz", - "integrity": "sha512-hBWqdZX+dioMWnTjf8+uSm0q1wCdYO4kU5gERzHcMMbu0Qg7JDR42TnJ6GHJ6r7k/tIpsCSygc9U0ehAtR24TQ==", - "dev": true, - "dependencies": { - "base16": "^1.0.0" - } - }, "node_modules/redux-persist": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/redux-persist/-/redux-persist-6.0.0.tgz", @@ -25250,9 +25171,9 @@ } }, "node_modules/sc-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/sc-errors/-/sc-errors-2.0.1.tgz", - "integrity": "sha512-JoVhq3Ud+3Ujv2SIG7W0XtjRHsrNgl6iXuHHsh0s+Kdt5NwI6N2EGAZD4iteitdDv68ENBkpjtSvN597/wxPSQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/sc-errors/-/sc-errors-2.0.3.tgz", + "integrity": "sha512-HNpClBWpo7zxLBnhH0U/FbC19Gl3OJlVyPxo9Q2eomfdWgYfd84uhqe0LRgybc+nSpcYjtF08+/dKPLugLMMeQ==", "dev": true }, "node_modules/schema-utils": { @@ -25815,21 +25736,21 @@ } }, "node_modules/socketcluster-server": { - "version": "17.4.1", - "resolved": "https://registry.npmjs.org/socketcluster-server/-/socketcluster-server-17.4.1.tgz", - "integrity": "sha512-ElKD9U7EncoWNGYOL+G6UAxuYmui1fnawpyhZIpFG/A/lDNGwHsQLNIpJXh1SB4BpoQLn2q4ewwnUK3Mse5mfA==", + "version": "19.0.1", + "resolved": "https://registry.npmjs.org/socketcluster-server/-/socketcluster-server-19.0.1.tgz", + "integrity": "sha512-IfMZxTkzvqOUExqiVxkxE2DjN/tap6WpbplatptsHKa58wfN6SdfcYCYeR3hlUBOx+cG09/hPxlN+R5tYZF0Mg==", "dev": true, "dependencies": { "ag-auth": "^2.0.0", - "ag-request": "^1.0.0", - "ag-simple-broker": "^5.0.0", - "async-stream-emitter": "^4.0.0", + "ag-request": "^1.0.1", + "ag-simple-broker": "^6.0.0", + "async-stream-emitter": "^7.0.1", "base64id": "^2.0.0", "clone-deep": "^4.0.1", - "sc-errors": "^2.0.1", + "sc-errors": "^2.0.3", "sc-formatter": "^4.0.0", - "stream-demux": "^8.0.0", - "writable-consumable-stream": "^2.0.0", + "stream-demux": "^10.0.1", + "writable-consumable-stream": "^4.1.0", "ws": "^8.9.0" } }, @@ -25840,9 +25761,9 @@ "dev": true }, "node_modules/socketcluster-server/node_modules/ws": { - "version": "8.14.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.1.tgz", - "integrity": "sha512-4OOseMUq8AzRBI/7SLMUwO+FEDnguetSk7KMb1sHwvF2w2Wv5Hoj0nlifx8vtGsftE/jWHojPy8sMMzYLJ2G/A==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", + "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", "dev": true, "engines": { "node": ">=10.0.0" @@ -26216,23 +26137,20 @@ } }, "node_modules/stream-demux": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/stream-demux/-/stream-demux-8.1.0.tgz", - "integrity": "sha512-20vtOmAj2EVzQZKZVmfyio16u/3QOKSvg+0ldgZeS+m2FNI1vKFoqggamagsPCXufdZ1Tk8VvAM/HV/YUmRbSg==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/stream-demux/-/stream-demux-10.0.1.tgz", + "integrity": "sha512-QjTYLJWpZxZ6uL5R1JzgOzjvao8zDx78ec+uOjHNeVc/9TuasYLldoVrYARZeT1xI1hFYuiKf13IM8b4wamhHg==", "dev": true, "dependencies": { - "consumable-stream": "^2.0.0", - "writable-consumable-stream": "^3.0.1" + "consumable-stream": "^3.0.0", + "writable-consumable-stream": "^4.1.0" } }, - "node_modules/stream-demux/node_modules/writable-consumable-stream": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/writable-consumable-stream/-/writable-consumable-stream-3.0.1.tgz", - "integrity": "sha512-rAOJTA/sMgXD/X6eMwbQJe49w+Fnkdx3iV5oUzdmiZ7Bwx03khqUnAKIpzp/hbI8q2EP5NfjXgIXN0MsipfHeg==", - "dev": true, - "dependencies": { - "consumable-stream": "^2.0.0" - } + "node_modules/stream-demux/node_modules/consumable-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/consumable-stream/-/consumable-stream-3.0.0.tgz", + "integrity": "sha512-CnnsJ9OG9ouxAjt3pc63/DaerezRo/WudqU71pc5epaIUi7NHu2T4v+3f0nKbbCY7icS/TfQ1Satr9rwZ7Jwsg==", + "dev": true }, "node_modules/stream-shift": { "version": "1.0.1", @@ -26648,15 +26566,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/symbol-observable": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", - "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", @@ -26928,12 +26837,6 @@ "node": ">=8" } }, - "node_modules/tiny-warning": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", - "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==", - "dev": true - }, "node_modules/titleize": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/titleize/-/titleize-3.0.0.tgz", @@ -29304,14 +29207,20 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "node_modules/writable-consumable-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/writable-consumable-stream/-/writable-consumable-stream-2.0.0.tgz", - "integrity": "sha512-SITambzxtPTFU/wR82h+zOKGBiEv5V8gC1mt8xvoE1/168ApEa8H+6s2UToYJo3VLL7sNYTaApKuPD+pZHMGJQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/writable-consumable-stream/-/writable-consumable-stream-4.1.0.tgz", + "integrity": "sha512-4cjCPd4Ayfbix0qqPCzMbnPPZKRh/cKeNCj05unybP3/sRkRAOxh7rSwbhxs3YB6G4/Z2p/2FRBEIQcTeB4jyw==", "dev": true, "dependencies": { - "consumable-stream": "^2.0.0" + "consumable-stream": "^3.0.0" } }, + "node_modules/writable-consumable-stream/node_modules/consumable-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/consumable-stream/-/consumable-stream-3.0.0.tgz", + "integrity": "sha512-CnnsJ9OG9ouxAjt3pc63/DaerezRo/WudqU71pc5epaIUi7NHu2T4v+3f0nKbbCY7icS/TfQ1Satr9rwZ7Jwsg==", + "dev": true + }, "node_modules/write-file-atomic": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", @@ -29627,9 +29536,9 @@ } }, "@apollo/server": { - "version": "4.9.3", - "resolved": "https://registry.npmjs.org/@apollo/server/-/server-4.9.3.tgz", - "integrity": "sha512-U56Sx/UmzR3Es344hQ/Ptf2EJrH+kV4ZPoLmgGjWoiwf2wYQ/pRSvkSXgjOvoyE34wSa8Gh7f92ljfLfY+6q1w==", + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/@apollo/server/-/server-4.9.5.tgz", + "integrity": "sha512-eDBfArYbZaTm1AGa82M1aL7lOscVhnZsH85+OWmHMIR98qntzEjNpWpQPYDTru63Qxs4kHcY29NUx/kMGZfGEA==", "dev": true, "requires": { "@apollo/cache-control-types": "^1.0.3", @@ -31465,9 +31374,9 @@ "integrity": "sha512-igX9a37DR2ZPGYtV6suZ6whr8pTFtyHL3K/oLUotxpSVO2ASaprmAe2Dkq7tBo7CRY7MMDrAa9nuQP9/YG8FxQ==" }, "@emotion/react": { - "version": "11.11.3", - "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.11.3.tgz", - "integrity": "sha512-Cnn0kuq4DoONOMcnoVsTOR8E+AdnKFf//6kUWc4LCdnxj31pZWn7rIULd6Y7/Js1PiPHzn7SKCM9vB/jBni8eA==", + "version": "11.11.4", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.11.4.tgz", + "integrity": "sha512-t8AjMlF0gHpvvxk5mAtCqR4vmxiGHCeJBaQO6gncUSdklELOgtwjerNY2yuJNfwnc6vi16U/+uMF+afIawJ9iw==", "dev": true, "requires": { "@babel/runtime": "^7.18.3", @@ -33073,254 +32982,287 @@ "dev": true }, "@redux-devtools/cli": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@redux-devtools/cli/-/cli-3.0.1.tgz", - "integrity": "sha512-NARxbTvLQQkZ4if5zvQTkTyesKCsu9DpN9Uupz1Ib18lUEuuEYl3HKJFYQ/hScnxxMSin7KN8j8guHMVD16pqg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@redux-devtools/cli/-/cli-4.0.0.tgz", + "integrity": "sha512-MACJmhnNS8Q0P8ldYgH+p7//U6QdetQqnIS5KZRzd8sp4rYSegzTqsQuf4eGZ4PSzrVOoVezj9eH6ESZFm9/9Q==", "dev": true, "requires": { - "@apollo/server": "^4.9.1", - "@redux-devtools/app": "^3.0.0", - "@types/react": "^18.2.20", + "@apollo/server": "^4.9.5", + "@emotion/react": "^11.11.3", + "@redux-devtools/app": "^6.0.0", + "@types/react": "^18.2.46", "body-parser": "^1.20.2", "chalk": "^5.3.0", "cors": "^2.8.5", "cross-spawn": "^7.0.3", - "electron": "^25.6.0", + "electron": "^27.2.0", "express": "^4.18.2", "get-port": "^7.0.0", - "graphql": "^16.8.0", - "knex": "^2.5.1", + "graphql": "^16.8.1", + "knex": "^3.1.0", "lodash-es": "^4.17.21", "minimist": "^1.2.8", "morgan": "^1.10.0", - "open": "^9.1.0", + "open": "^10.0.2", "react": "^18.2.0", "react-dom": "^18.2.0", "react-is": "^18.2.0", "semver": "^7.5.4", - "socketcluster-server": "^17.4.1", + "socketcluster-server": "^19.0.1", "sqlite3": "^5.1.6", "styled-components": "^5.3.11", - "uuid": "^9.0.0" + "uuid": "^9.0.1" }, "dependencies": { "@babel/code-frame": { - "version": "8.0.0-alpha.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-8.0.0-alpha.6.tgz", - "integrity": "sha512-w29LUiFqVMe+Ybp/+xtQKAp6rpxjHxtCW909I0OMHvFKzrGB0K+1yQGRzXhTqrd4f7ZRRgzkAxdr0EOEbHt3dA==", + "version": "8.0.0-alpha.8", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-8.0.0-alpha.8.tgz", + "integrity": "sha512-PaXRlxddNNx+S+OZ/laLOqhiqaDgBQCvxR/Z0CHcpXgPRmtBNbRxvWhf91+YtST1O+O6j2K0F4bb5DvA3Hr4gQ==", "dev": true, "requires": { - "@babel/highlight": "^8.0.0-alpha.6", - "chalk": "^5.3.0" + "@babel/highlight": "^8.0.0-alpha.8", + "picocolors": "^1.0.0" } }, "@babel/helper-validator-identifier": { - "version": "8.0.0-alpha.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.0-alpha.6.tgz", - "integrity": "sha512-a0YnAx4TJCgds07M2v0A3WeyZUfls2YOgNHCb1akGKlz5KdmTCqf2N2hw86YhjY6oDqb6Omb8gFKz/3TOKRevQ==", + "version": "8.0.0-alpha.8", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.0-alpha.8.tgz", + "integrity": "sha512-7uCkkKAnoPa864zeoJ82mhXNVhRtvVbquSZ+i98qG4jaUu3yRe3ZKWV1K075FTthM4gNgRVMWTK/KjrSuX9w+Q==", "dev": true }, "@babel/highlight": { - "version": "8.0.0-alpha.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-8.0.0-alpha.6.tgz", - "integrity": "sha512-YzJ5blZXak7Gk6ZgV9a4B9bAkGzl594ZxmBaFFZkstA5hE1iOBTlUW4tkGKz2uomL7FLLW4qHJF3QjQLHKi7iw==", + "version": "8.0.0-alpha.8", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-8.0.0-alpha.8.tgz", + "integrity": "sha512-1xw7Z/AMD4ZJxxktv1sXClheNGab1BAU11SND6AiwUyhIAOVrvQ9QiOBd8I/iMXdXtANCNyu0YLswn3TNlcRZw==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^8.0.0-alpha.6", - "chalk": "^5.3.0", - "js-tokens": "^8.0.0" + "@babel/helper-validator-identifier": "^8.0.0-alpha.8", + "js-tokens": "^8.0.0", + "picocolors": "^1.0.0" } }, "@babel/runtime": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.9.tgz", - "integrity": "sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.6.tgz", + "integrity": "sha512-Ja18XcETdEl5mzzACGd+DKgaGJzPTCow7EglgwTmHdwokzDFYh/MHua6lU6DV/hjF2IaOJ4oX2nqnjG7RElKOw==", "dev": true, "requires": { "regenerator-runtime": "^0.14.0" } }, "@redux-devtools/app": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@redux-devtools/app/-/app-3.0.0.tgz", - "integrity": "sha512-3mn+cj3KhMRSa0EPxlRD4EnfFFMBsM1nKJ+g/6zB4iRyk23befz4YzwFG3KXkwyVmZCooCu3DjHkQhUOx4xxQA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.22.10", - "@redux-devtools/chart-monitor": "^4.0.0", - "@redux-devtools/core": "^3.13.0", - "@redux-devtools/inspector-monitor": "^4.0.0", - "@redux-devtools/inspector-monitor-test-tab": "^2.0.0", - "@redux-devtools/inspector-monitor-trace-tab": "^2.0.0", - "@redux-devtools/log-monitor": "^4.0.2", - "@redux-devtools/rtk-query-monitor": "^3.1.1", - "@redux-devtools/slider-monitor": "^4.0.0", - "@redux-devtools/ui": "^1.3.0", - "@reduxjs/toolkit": "^1.9.5", - "@types/prop-types": "^15.7.5", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@redux-devtools/app/-/app-6.0.0.tgz", + "integrity": "sha512-jR6Nm/Pe/ImQ2XmJC1JIyVduQ/3J4bXQhtvoH/pw8WuVJrQvFzhfY5McS+LbHB6H+Vjfhq7mq3u1T2dcQ6II3g==", + "dev": true, + "requires": { + "@babel/runtime": "^7.23.5", + "@redux-devtools/chart-monitor": "^5.0.0", + "@redux-devtools/core": "^4.0.0", + "@redux-devtools/inspector-monitor": "^6.0.0", + "@redux-devtools/inspector-monitor-test-tab": "^4.0.0", + "@redux-devtools/inspector-monitor-trace-tab": "^4.0.0", + "@redux-devtools/log-monitor": "^5.0.0", + "@redux-devtools/rtk-query-monitor": "^5.0.0", + "@redux-devtools/slider-monitor": "^5.0.0", + "@redux-devtools/ui": "^1.3.1", + "@reduxjs/toolkit": "^1.9.7", "d3-state-visualizer": "^2.0.0", "javascript-stringify": "^2.1.0", "jsan": "^3.1.14", "jsondiffpatch": "^0.5.0", "localforage": "^1.10.0", "lodash": "^4.17.21", - "prop-types": "^15.8.1", - "react-icons": "^4.10.1", + "react-icons": "^4.12.0", "react-is": "^18.2.0", - "react-redux": "^8.1.2", - "redux": "4.2.1", + "react-redux": "^8.1.3", + "redux": "^4.2.1", "redux-persist": "^6.0.0", "socketcluster-client": "^17.2.2" - } - }, - "@redux-devtools/chart-monitor": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@redux-devtools/chart-monitor/-/chart-monitor-4.1.0.tgz", - "integrity": "sha512-fdW7DKEhCB9oumMTyhdrVGOgb/6lBioPveW3w0+u7MCZn2i32laZYzU9VT39sx+Fc2kwxzbYsLxFsU0tijGsXA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.23.2", - "@types/redux-devtools-themes": "^1.0.3", - "d3-state-visualizer": "^2.0.0", - "deepmerge": "^4.3.1", - "redux-devtools-themes": "^1.0.0" - } - }, - "@redux-devtools/core": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/@redux-devtools/core/-/core-3.14.0.tgz", - "integrity": "sha512-OMPflPPCXR9L1rpfd7gwY31/EuqPyE9Of/5wZgDDzeisaENY5h/EfnAjnHRKr7NIx/yUIUX2DJs8NpmUOnANMg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.23.2", - "@redux-devtools/instrument": "^2.2.0", - "@types/prop-types": "^15.7.10", - "lodash": "^4.17.21", - "prop-types": "^15.8.1" + }, + "dependencies": { + "@redux-devtools/chart-monitor": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@redux-devtools/chart-monitor/-/chart-monitor-5.0.2.tgz", + "integrity": "sha512-5S70WvWufMa8ybwpFqxGqA65Mb8xV7e1gviKnGp6KGDn+EYo2bu+O+o5ugt/4WW4z4HgeXkNl3DawwjTUNc9aw==", + "dev": true, + "requires": { + "@babel/runtime": "^7.24.1", + "d3-state-visualizer": "^3.0.0", + "deepmerge": "^4.3.1", + "react-base16-styling": "^0.10.0" + }, + "dependencies": { + "d3-state-visualizer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-state-visualizer/-/d3-state-visualizer-3.0.0.tgz", + "integrity": "sha512-69syDmi4b6sr9/UJUVpKTzFFx7jeI8AogEWv2qL/AWKky9b9NgqgMtSBbJNQdncq6epsOKP7a2MDun90taHtTQ==", + "dev": true, + "requires": { + "@types/d3": "^7.4.3", + "d3": "^7.9.0", + "d3tooltip": "^4.0.0", + "deepmerge": "^4.3.1", + "map2tree": "^4.0.0", + "ramda": "^0.29.1" + } + } + } + }, + "@redux-devtools/inspector-monitor-test-tab": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@redux-devtools/inspector-monitor-test-tab/-/inspector-monitor-test-tab-4.0.0.tgz", + "integrity": "sha512-a5SuX6eyFvBAK9UcRW/n5K8nSISWR+UGW0+fZh+KP20naSgkvagrzC705NX8riCfJc8fr5csIYVYwmypMAZ2UA==", + "dev": true, + "requires": { + "@babel/runtime": "^7.23.5", + "@redux-devtools/ui": "^1.3.1", + "es6template": "^1.0.5", + "javascript-stringify": "^2.1.0", + "jsan": "^3.1.14", + "object-path": "^0.11.8", + "react-icons": "^4.12.0", + "simple-diff": "^1.7.2" + } + }, + "@redux-devtools/inspector-monitor-trace-tab": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@redux-devtools/inspector-monitor-trace-tab/-/inspector-monitor-trace-tab-4.0.1.tgz", + "integrity": "sha512-7tu/JU9wmpp74sBlBirgikrjJsx3IoJBKzUj9lO9ox9U9cJ/MhSo9B8ZAmLDI8EJa3CzxCcNQnDxW/Wucy17DQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^8.0.0-alpha.7", + "@babel/runtime": "^7.24.1", + "@types/chrome": "^0.0.263", + "anser": "^2.1.1", + "html-entities": "^2.5.2", + "path-browserify": "^1.0.1", + "react-base16-styling": "^0.10.0", + "source-map": "^0.5.7" + } + }, + "@redux-devtools/log-monitor": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@redux-devtools/log-monitor/-/log-monitor-5.0.1.tgz", + "integrity": "sha512-NCuKr9hdruL01rbghKs/eD2lLcKhXmICXf2js2gTM9oDA2662+X9M9BahcJwjhkTUBxdsXbDQ5d/uGvxr530Vg==", + "dev": true, + "requires": { + "@babel/runtime": "^7.24.1", + "@types/lodash.debounce": "^4.0.9", + "lodash.debounce": "^4.0.8", + "react-base16-styling": "^0.10.0", + "react-json-tree": "^0.19.0" + } + }, + "@redux-devtools/rtk-query-monitor": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@redux-devtools/rtk-query-monitor/-/rtk-query-monitor-5.0.1.tgz", + "integrity": "sha512-mqYkEop8lgyswW4HxEDnPBsdxU/h3Rm+/q0KCbeuVSZU7Ek/gEgSnKAi++JvsTMPekgA1Uie94/kztOrB6Kv/w==", + "dev": true, + "requires": { + "@babel/runtime": "^7.24.1", + "@redux-devtools/ui": "^1.3.2", + "@types/lodash": "^4.17.0", + "hex-rgba": "^1.0.2", + "immutable": "^4.3.5", + "lodash.debounce": "^4.0.8", + "react-base16-styling": "^0.10.0", + "react-json-tree": "^0.19.0" + } + }, + "@redux-devtools/slider-monitor": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@redux-devtools/slider-monitor/-/slider-monitor-5.0.1.tgz", + "integrity": "sha512-ud04CLxJNMpNfXKrfHtt4i7LbYCi3PVdcrQgU0X3qeEUWiXkvumZJIs0DPKSMk3Ukqy/G06SMNL4aqy6icjkLQ==", + "dev": true, + "requires": { + "@babel/runtime": "^7.24.1", + "@redux-devtools/ui": "^1.3.2", + "react-base16-styling": "^0.10.0" + } + } } }, "@redux-devtools/inspector-monitor": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@redux-devtools/inspector-monitor/-/inspector-monitor-4.1.0.tgz", - "integrity": "sha512-ga0rBdb8LeB7mN+X+/Pb0rx+yTPOsSHFFVxgcWag7XsBu+dXtujlRsCM++6LX6GjJGNzZAUsG4ZI4Wt0dG2CQg==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@redux-devtools/inspector-monitor/-/inspector-monitor-6.0.1.tgz", + "integrity": "sha512-FGUOrV3hEKrelqGSWmExKbBfXTEtLQ1yj+gLuPY2yAnZwFwOKzxybL5tsHCvVZsm/hgkA3WGcpYHtZ6+pJSWBg==", "dev": true, "requires": { - "@babel/runtime": "^7.23.2", + "@babel/runtime": "^7.24.1", "@dnd-kit/core": "^6.1.0", "@dnd-kit/modifiers": "^7.0.0", "@dnd-kit/sortable": "^8.0.0", "@dnd-kit/utilities": "^3.2.2", - "@types/lodash": "^4.14.201", - "@types/prop-types": "^15.7.10", - "@types/redux-devtools-themes": "^1.0.3", + "@types/lodash": "^4.17.0", "dateformat": "^5.0.3", "hex-rgba": "^1.0.2", - "immutable": "^4.3.4", + "immutable": "^4.3.5", "javascript-stringify": "^2.1.0", - "jsondiffpatch": "^0.5.0", - "jss": "^10.10.0", - "jss-preset-default": "^10.10.0", + "jsondiffpatch": "^0.6.0", "lodash.debounce": "^4.0.8", - "prop-types": "^15.8.1", - "react-base16-styling": "^0.9.1", - "react-json-tree": "^0.18.0", - "redux-devtools-themes": "^1.0.0" - } - }, - "@redux-devtools/inspector-monitor-test-tab": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@redux-devtools/inspector-monitor-test-tab/-/inspector-monitor-test-tab-2.1.0.tgz", - "integrity": "sha512-wQ8dnB1F7RZn4XFoKYVPUPt996CIGWG1/ndQwGkXPvHg+P32QHUHsrhR+QDpn6Z1dMamycDjqijS2KDkQA/MDg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.23.2", - "@redux-devtools/ui": "^1.3.1", - "@types/prop-types": "^15.7.10", - "es6template": "^1.0.5", - "javascript-stringify": "^2.1.0", - "jsan": "^3.1.14", - "object-path": "^0.11.8", - "prop-types": "^15.8.1", - "react-icons": "^4.11.0", - "simple-diff": "^1.7.2" - } - }, - "@redux-devtools/inspector-monitor-trace-tab": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@redux-devtools/inspector-monitor-trace-tab/-/inspector-monitor-trace-tab-2.1.0.tgz", - "integrity": "sha512-Sac8iyYahyrq3k76kty1ebMVCp7Q1ABdBdzj8vJ5FSiaXOKU+HY1y4YS6Fb9ra7Bj5udIqzr/zdxprdPc7Yatg==", - "dev": true, - "requires": { - "@babel/code-frame": "^8.0.0-alpha.4", - "@babel/runtime": "^7.23.2", - "@types/chrome": "^0.0.251", - "anser": "^2.1.1", - "html-entities": "^2.4.0", - "path-browserify": "^1.0.1", - "redux-devtools-themes": "^1.0.0", - "source-map": "^0.5.7" + "react-base16-styling": "^0.10.0", + "react-json-tree": "^0.19.0" + }, + "dependencies": { + "jsondiffpatch": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/jsondiffpatch/-/jsondiffpatch-0.6.0.tgz", + "integrity": "sha512-3QItJOXp2AP1uv7waBkao5nCvhEv+QmJAd38Ybq7wNI74Q+BBmnLn4EDKz6yI9xGAIQoUF87qHt+kc1IVxB4zQ==", + "dev": true, + "requires": { + "@types/diff-match-patch": "^1.0.36", + "chalk": "^5.3.0", + "diff-match-patch": "^1.0.5" + } + } } }, - "@redux-devtools/log-monitor": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@redux-devtools/log-monitor/-/log-monitor-4.1.0.tgz", - "integrity": "sha512-2danca7yfQnuyzYxUVsEontmcRfQLFZgU8M33690JfvHlUV1OYjkNhbwKkMEso5py8sTd2EgoC2PGySoyupbUg==", + "@redux-devtools/ui": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@redux-devtools/ui/-/ui-1.3.2.tgz", + "integrity": "sha512-/g9FSzvOSCkTXibd+h13kRE433/Zv4KVImQaanJsl8fc0MEfBSyFtGE1wjeIkwqs322EVBxw3fPWaQAC1gpLHg==", "dev": true, "requires": { - "@babel/runtime": "^7.23.2", - "@types/lodash.debounce": "^4.0.9", - "@types/prop-types": "^15.7.10", - "@types/redux-devtools-themes": "^1.0.3", - "lodash.debounce": "^4.0.8", - "prop-types": "^15.8.1", - "react-json-tree": "^0.18.0", - "redux-devtools-themes": "^1.0.0" + "@babel/runtime": "^7.24.1", + "@rjsf/core": "^4.2.3", + "@types/codemirror": "^5.60.15", + "@types/json-schema": "^7.0.15", + "@types/simple-element-resize-detector": "^1.3.3", + "codemirror": "^5.65.16", + "color": "^4.2.3", + "react-base16-styling": "^0.10.0", + "react-icons": "^5.0.1", + "react-select": "^5.8.0", + "simple-element-resize-detector": "^1.3.0" + }, + "dependencies": { + "react-icons": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.2.1.tgz", + "integrity": "sha512-zdbW5GstTzXaVKvGSyTaBalt7HSfuK5ovrzlpyiWHAFXndXTdd/1hdDHI4xBM1Mn7YriT6aqESucFl9kEXzrdw==", + "dev": true, + "requires": {} + } } }, - "@redux-devtools/rtk-query-monitor": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@redux-devtools/rtk-query-monitor/-/rtk-query-monitor-3.2.0.tgz", - "integrity": "sha512-Td7oZUAO/TyxillkC2E33vYqfKEDFilJfLHLg8Na7KdfG3Y0JR5qmu0xypZDODeEsA5teHpkMnhI3O9ew/JqdQ==", + "@types/chrome": { + "version": "0.0.263", + "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.263.tgz", + "integrity": "sha512-As0vzv99ov3M6ZR7R6VzhMWFZXkPMrFrCEXXVrMN576Cm70fTkj7Df2CF+qEo170JepX50pd11cX6O4DSAtl2Q==", "dev": true, "requires": { - "@babel/runtime": "^7.23.2", - "@redux-devtools/ui": "^1.3.1", - "@types/lodash": "^4.14.201", - "@types/prop-types": "^15.7.10", - "@types/redux-devtools-themes": "^1.0.3", - "hex-rgba": "^1.0.2", - "immutable": "^4.3.4", - "jss": "^10.10.0", - "jss-preset-default": "^10.10.0", - "lodash.debounce": "^4.0.8", - "prop-types": "^15.8.1", - "react-base16-styling": "^0.9.1", - "react-json-tree": "^0.18.0", - "redux-devtools-themes": "^1.0.0" + "@types/filesystem": "*", + "@types/har-format": "*" } }, - "@redux-devtools/slider-monitor": { + "async-stream-emitter": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@redux-devtools/slider-monitor/-/slider-monitor-4.1.0.tgz", - "integrity": "sha512-p0jDgLcmuOQyJTlMAwjSZ8wxioqV802IXtm7BsxGq9021WsmyhEGRhqfu4XvXUt95sBHNsRsm9yE5XVTMmlAtw==", + "resolved": "https://registry.npmjs.org/async-stream-emitter/-/async-stream-emitter-4.1.0.tgz", + "integrity": "sha512-cfPZYjHkhCdHSR+eux71vOU8+8Xb23oLyxccAjwYHgOxDb3+qSDb2HV1Y0Hmu39vZlse2cm15CUShLiVYXHCmQ==", "dev": true, "requires": { - "@babel/runtime": "^7.23.2", - "@redux-devtools/ui": "^1.3.1", - "@types/prop-types": "^15.7.10", - "@types/redux-devtools-themes": "^1.0.3", - "prop-types": "^15.8.1", - "redux-devtools-themes": "^1.0.0" - } - }, - "@types/chrome": { - "version": "0.0.251", - "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.251.tgz", - "integrity": "sha512-UF+yr0LEKWWGsKxQ5A3XOSF5SNoU1ctW3pXcWJPpT8OOUTEspYeaLU8spDKe+6xalXeMTS0TBrX1g0b6qlWmkw==", - "dev": true, - "requires": { - "@types/filesystem": "*", - "@types/har-format": "*" + "stream-demux": "^8.1.0" } }, "body-parser": { @@ -33343,6 +33285,15 @@ "unpipe": "1.0.0" } }, + "bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, + "requires": { + "run-applescript": "^7.0.0" + } + }, "chalk": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", @@ -33355,6 +33306,13 @@ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "dev": true }, + "d3tooltip": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/d3tooltip/-/d3tooltip-4.0.0.tgz", + "integrity": "sha512-jQRmF04fL9G+sROeN1/CZaHRqE31Pl7Qky6qIJ2MkecTwXZtgfDgmgrPZ0VS5Ewnj9sYWJ6CAl6fYI4PNn2Fpw==", + "dev": true, + "requires": {} + }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -33364,18 +33322,52 @@ "ms": "2.0.0" } }, + "default-browser": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz", + "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==", + "dev": true, + "requires": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + } + }, + "default-browser-id": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz", + "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==", + "dev": true + }, "define-lazy-prop": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", "dev": true }, + "is-wsl": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "dev": true, + "requires": { + "is-inside-container": "^1.0.0" + } + }, "js-tokens": { "version": "8.0.3", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-8.0.3.tgz", "integrity": "sha512-UfJMcSJc+SEXEl9lH/VLHSZbThQyLpw1vLO1Lb+j4RWDvG3N2f7yj3PVQA3cmkTBNldJ9eFnM+xEXxHIXrYiJw==", "dev": true }, + "map2tree": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/map2tree/-/map2tree-4.0.0.tgz", + "integrity": "sha512-nzoeFAjW5yCwLFpV3A6EZS3kHD6H9y82S0KANc8tN2Mha+Ad2vIggDqxxYdI8lgghHzQL3yf/6tqlcoplRw8NA==", + "dev": true, + "requires": { + "lodash-es": "^4.17.21" + } + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -33383,17 +33375,23 @@ "dev": true }, "open": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/open/-/open-9.1.0.tgz", - "integrity": "sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg==", + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/open/-/open-10.0.2.tgz", + "integrity": "sha512-GnYLdE+E3K8NeSE23N0g67/9q9AXRph5oTUbz6IbIgElPigEnQ2aHuqRge3y0JUr67qoc84xME5kF03fDc3fcA==", "dev": true, "requires": { - "default-browser": "^4.0.0", + "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", - "is-wsl": "^2.2.0" + "is-wsl": "^3.1.0" } }, + "ramda": { + "version": "0.29.1", + "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.29.1.tgz", + "integrity": "sha512-OfxIeWzd4xdUNxlWhgFazxsA/nl3mS4/jGZI5n00uWOoSSFRhC1b6gl6xvmzUamgmqELraWp0J/qqVlXYPDPyA==", + "dev": true + }, "raw-body": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", @@ -33407,9 +33405,9 @@ } }, "react-redux": { - "version": "8.1.2", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.1.2.tgz", - "integrity": "sha512-xJKYI189VwfsFc4CJvHqHlDrzyFTY/3vZACbE+rr/zQ34Xx1wQfB4OTOSeOSNrF6BDVe8OOdxIrAnMGXA3ggfw==", + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.1.3.tgz", + "integrity": "sha512-n0ZrutD7DaX/j9VscF+uTALI3oUPa/pO4Z3soOBIjuRn/FzVu6aehhysxZCLi6y7duMf52WNZGMl7CtuK5EnRw==", "dev": true, "requires": { "@babel/runtime": "^7.12.1", @@ -33421,9 +33419,15 @@ } }, "regenerator-runtime": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", - "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==", + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "dev": true + }, + "run-applescript": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz", + "integrity": "sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==", "dev": true }, "sc-formatter": { @@ -33469,6 +33473,16 @@ } } }, + "stream-demux": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/stream-demux/-/stream-demux-8.1.0.tgz", + "integrity": "sha512-20vtOmAj2EVzQZKZVmfyio16u/3QOKSvg+0ldgZeS+m2FNI1vKFoqggamagsPCXufdZ1Tk8VvAM/HV/YUmRbSg==", + "dev": true, + "requires": { + "consumable-stream": "^2.0.0", + "writable-consumable-stream": "^3.0.1" + } + }, "styled-components": { "version": "5.3.11", "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.11.tgz", @@ -33493,10 +33507,19 @@ "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", "dev": true }, + "writable-consumable-stream": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/writable-consumable-stream/-/writable-consumable-stream-3.0.1.tgz", + "integrity": "sha512-rAOJTA/sMgXD/X6eMwbQJe49w+Fnkdx3iV5oUzdmiZ7Bwx03khqUnAKIpzp/hbI8q2EP5NfjXgIXN0MsipfHeg==", + "dev": true, + "requires": { + "consumable-stream": "^2.0.0" + } + }, "ws": { - "version": "8.14.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.1.tgz", - "integrity": "sha512-4OOseMUq8AzRBI/7SLMUwO+FEDnguetSk7KMb1sHwvF2w2Wv5Hoj0nlifx8vtGsftE/jWHojPy8sMMzYLJ2G/A==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", + "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", "dev": true, "requires": {} } @@ -33581,6 +33604,15 @@ "regenerator-runtime": "^0.14.0" } }, + "async-stream-emitter": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/async-stream-emitter/-/async-stream-emitter-4.1.0.tgz", + "integrity": "sha512-cfPZYjHkhCdHSR+eux71vOU8+8Xb23oLyxccAjwYHgOxDb3+qSDb2HV1Y0Hmu39vZlse2cm15CUShLiVYXHCmQ==", + "dev": true, + "requires": { + "stream-demux": "^8.1.0" + } + }, "regenerator-runtime": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", @@ -33613,6 +33645,25 @@ "ws": "^8.9.0" } }, + "stream-demux": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/stream-demux/-/stream-demux-8.1.0.tgz", + "integrity": "sha512-20vtOmAj2EVzQZKZVmfyio16u/3QOKSvg+0ldgZeS+m2FNI1vKFoqggamagsPCXufdZ1Tk8VvAM/HV/YUmRbSg==", + "dev": true, + "requires": { + "consumable-stream": "^2.0.0", + "writable-consumable-stream": "^3.0.1" + } + }, + "writable-consumable-stream": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/writable-consumable-stream/-/writable-consumable-stream-3.0.1.tgz", + "integrity": "sha512-rAOJTA/sMgXD/X6eMwbQJe49w+Fnkdx3iV5oUzdmiZ7Bwx03khqUnAKIpzp/hbI8q2EP5NfjXgIXN0MsipfHeg==", + "dev": true, + "requires": { + "consumable-stream": "^2.0.0" + } + }, "ws": { "version": "8.16.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", @@ -33649,47 +33700,6 @@ } } }, - "@redux-devtools/ui": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@redux-devtools/ui/-/ui-1.3.1.tgz", - "integrity": "sha512-qtfau1zSmv6nSUrnesuxaos1V7r89HQV2Hq/thcRcyHd0Y0OYgpQlnwEK3wkK1esLU2BhEUUb85DC8IdJ3l6Sg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.23.2", - "@rjsf/core": "^4.2.3", - "@types/base16": "^1.0.5", - "@types/codemirror": "^5.60.13", - "@types/json-schema": "^7.0.15", - "@types/prop-types": "^15.7.10", - "@types/redux-devtools-themes": "^1.0.3", - "@types/simple-element-resize-detector": "^1.3.3", - "base16": "^1.0.0", - "codemirror": "^5.65.15", - "color": "^4.2.3", - "prop-types": "^15.8.1", - "react-icons": "^4.11.0", - "react-select": "^5.8.0", - "redux-devtools-themes": "^1.0.0", - "simple-element-resize-detector": "^1.3.0" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.9.tgz", - "integrity": "sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==", - "dev": true, - "requires": { - "regenerator-runtime": "^0.14.0" - } - }, - "regenerator-runtime": { - "version": "0.14.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", - "dev": true - } - } - }, "@redux-devtools/utils": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@redux-devtools/utils/-/utils-3.0.0.tgz", @@ -34239,12 +34249,6 @@ "@babel/types": "^7.3.0" } }, - "@types/base16": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/base16/-/base16-1.0.5.tgz", - "integrity": "sha512-OzOWrTluG9cwqidEzC/Q6FAmIPcnZfm8BFRlIx0+UIUqnuAmi5OS88O0RpT3Yz6qdmqObvUhasrbNsCofE4W9A==", - "dev": true - }, "@types/big.js": { "version": "6.1.6", "resolved": "https://registry.npmjs.org/@types/big.js/-/big.js-6.1.6.tgz", @@ -34329,9 +34333,9 @@ } }, "@types/d3": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.0.tgz", - "integrity": "sha512-jIfNVK0ZlxcuRDKtRS/SypEyOQ6UHaFQBKv032X45VvxSJ6Yi5G9behy9h6tNTHTDGh5Vq+KbmBjUWLgY4meCA==", + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", + "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", "dev": true, "requires": { "@types/d3-array": "*", @@ -34367,45 +34371,45 @@ } }, "@types/d3-array": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.0.7.tgz", - "integrity": "sha512-4/Q0FckQ8TBjsB0VdGFemJOG8BLXUB2KKlL0VmZ+eOYeOnTb/wDRQqYWpBmQ6IlvWkXwkYiot+n9Px2aTJ7zGQ==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz", + "integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==", "dev": true }, "@types/d3-axis": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.3.tgz", - "integrity": "sha512-SE3x/pLO/+GIHH17mvs1uUVPkZ3bHquGzvZpPAh4yadRy71J93MJBpgK/xY8l9gT28yTN1g9v3HfGSFeBMmwZw==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz", + "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==", "dev": true, "requires": { "@types/d3-selection": "*" } }, "@types/d3-brush": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.3.tgz", - "integrity": "sha512-MQ1/M/B5ifTScHSe5koNkhxn2mhUPqXjGuKjjVYckplAPjP9t2I2sZafb/YVHDwhoXWZoSav+Q726eIbN3qprA==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz", + "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", "dev": true, "requires": { "@types/d3-selection": "*" } }, "@types/d3-chord": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.3.tgz", - "integrity": "sha512-keuSRwO02c7PBV3JMWuctIfdeJrVFI7RpzouehvBWL4/GGUB3PBNg/9ZKPZAgJphzmS2v2+7vr7BGDQw1CAulw==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz", + "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==", "dev": true }, "@types/d3-color": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.0.tgz", - "integrity": "sha512-HKuicPHJuvPgCD+np6Se9MQvS6OCbJmOjGvylzMJRlDwUXjKTTXs6Pwgk79O09Vj/ho3u1ofXnhFOaEWWPrlwA==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", "dev": true }, "@types/d3-contour": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.3.tgz", - "integrity": "sha512-x7G/tdDZt4m09XZnG2SutbIuQqmkNYqR9uhDMdPlpJbcwepkEjEWG29euFcgVA1k6cn92CHdDL9Z+fOnxnbVQw==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz", + "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", "dev": true, "requires": { "@types/d3-array": "*", @@ -34413,174 +34417,180 @@ } }, "@types/d3-delaunay": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.1.tgz", - "integrity": "sha512-tLxQ2sfT0p6sxdG75c6f/ekqxjyYR0+LwPrsO1mbC9YDBzPJhs2HbJJRrn8Ez1DBoHRo2yx7YEATI+8V1nGMnQ==", + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==", "dev": true }, "@types/d3-dispatch": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.3.tgz", - "integrity": "sha512-Df7KW3Re7G6cIpIhQtqHin8yUxUHYAqiE41ffopbmU5+FifYUNV7RVyTg8rQdkEagg83m14QtS8InvNb95Zqug==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.6.tgz", + "integrity": "sha512-4fvZhzMeeuBJYZXRXrRIQnvUYfyXwYmLsdiN7XXmVNQKKw1cM8a5WdID0g1hVFZDqT9ZqZEY5pD44p24VS7iZQ==", "dev": true }, "@types/d3-drag": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.3.tgz", - "integrity": "sha512-82AuQMpBQjuXeIX4tjCYfWjpm3g7aGCfx6dFlxX2JlRaiME/QWcHzBsINl7gbHCODA2anPYlL31/Trj/UnjK9A==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", "dev": true, "requires": { "@types/d3-selection": "*" } }, "@types/d3-dsv": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.2.tgz", - "integrity": "sha512-DooW5AOkj4AGmseVvbwHvwM/Ltu0Ks0WrhG6r5FG9riHT5oUUTHz6xHsHqJSVU8ZmPkOqlUEY2obS5C9oCIi2g==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", + "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==", "dev": true }, "@types/d3-ease": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.0.tgz", - "integrity": "sha512-aMo4eaAOijJjA6uU+GIeW018dvy9+oH5Y2VPPzjjfxevvGQ/oRDs+tfYC9b50Q4BygRR8yE2QCLsrT0WtAVseA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", "dev": true }, "@types/d3-fetch": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.3.tgz", - "integrity": "sha512-/EsDKRiQkby3Z/8/AiZq8bsuLDo/tYHnNIZkUpSeEHWV7fHUl6QFBjvMPbhkKGk9jZutzfOkGygCV7eR/MkcXA==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", + "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", "dev": true, "requires": { "@types/d3-dsv": "*" } }, "@types/d3-force": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.5.tgz", - "integrity": "sha512-EGG+IWx93ESSXBwfh/5uPuR9Hp8M6o6qEGU7bBQslxCvrdUBQZha/EFpu/VMdLU4B0y4Oe4h175nSm7p9uqFug==", + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.9.tgz", + "integrity": "sha512-IKtvyFdb4Q0LWna6ymywQsEYjK/94SGhPrMfEr1TIc5OBeziTi+1jcCvttts8e0UWZIxpasjnQk9MNk/3iS+kA==", "dev": true }, "@types/d3-format": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.1.tgz", - "integrity": "sha512-5KY70ifCCzorkLuIkDe0Z9YTf9RR2CjBX1iaJG+rgM/cPP+sO+q9YdQ9WdhQcgPj1EQiJ2/0+yUkkziTG6Lubg==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", + "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==", "dev": true }, "@types/d3-geo": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.0.4.tgz", - "integrity": "sha512-kmUK8rVVIBPKJ1/v36bk2aSgwRj2N/ZkjDT+FkMT5pgedZoPlyhaG62J+9EgNIgUXE6IIL0b7bkLxCzhE6U4VQ==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", "dev": true, "requires": { "@types/geojson": "*" } }, "@types/d3-hierarchy": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.3.tgz", - "integrity": "sha512-GpSK308Xj+HeLvogfEc7QsCOcIxkDwLhFYnOoohosEzOqv7/agxwvJER1v/kTC+CY1nfazR0F7gnHo7GE41/fw==", + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.6.tgz", + "integrity": "sha512-qlmD/8aMk5xGorUvTUWHCiumvgaUXYldYjNVOWtYoTYY/L+WwIEAmJxUmTgr9LoGNG0PPAOmqMDJVDPc7DOpPw==", "dev": true }, "@types/d3-interpolate": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.1.tgz", - "integrity": "sha512-jx5leotSeac3jr0RePOH1KdR9rISG91QIE4Q2PYTu4OymLTZfA3SrnURSLzKH48HmXVUru50b8nje4E79oQSQw==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", "dev": true, "requires": { "@types/d3-color": "*" } }, "@types/d3-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.0.0.tgz", - "integrity": "sha512-0g/A+mZXgFkQxN3HniRDbXMN79K3CdTpLsevj+PXiTcb2hVyvkZUBg37StmgCQkaD84cUJ4uaDAWq7UJOQy2Tg==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.0.2.tgz", + "integrity": "sha512-WAIEVlOCdd/NKRYTsqCpOMHQHemKBEINf8YXMYOtXH0GA7SY0dqMB78P3Uhgfy+4X+/Mlw2wDtlETkN6kQUCMA==", "dev": true }, "@types/d3-polygon": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.0.tgz", - "integrity": "sha512-D49z4DyzTKXM0sGKVqiTDTYr+DHg/uxsiWDAkNrwXYuiZVd9o9wXZIo+YsHkifOiyBkmSWlEngHCQme54/hnHw==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", + "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==", "dev": true }, "@types/d3-quadtree": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.2.tgz", - "integrity": "sha512-QNcK8Jguvc8lU+4OfeNx+qnVy7c0VrDJ+CCVFS9srBo2GL9Y18CnIxBdTF3v38flrGy5s1YggcoAiu6s4fLQIw==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", + "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==", "dev": true }, "@types/d3-random": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.1.tgz", - "integrity": "sha512-IIE6YTekGczpLYo/HehAy3JGF1ty7+usI97LqraNa8IiDur+L44d0VOjAvFQWJVdZOJHukUJw+ZdZBlgeUsHOQ==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz", + "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==", "dev": true }, "@types/d3-scale": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.4.tgz", - "integrity": "sha512-eq1ZeTj0yr72L8MQk6N6heP603ubnywSDRfNpi5enouR112HzGLS6RIvExCzZTraFF4HdzNpJMwA/zGiMoHUUw==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.8.tgz", + "integrity": "sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==", "dev": true, "requires": { "@types/d3-time": "*" } }, "@types/d3-scale-chromatic": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz", - "integrity": "sha512-dsoJGEIShosKVRBZB0Vo3C8nqSDqVGujJU6tPznsBJxNJNwMF8utmS83nvCBKQYPpjCzaaHcrf66iTRpZosLPw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.0.3.tgz", + "integrity": "sha512-laXM4+1o5ImZv3RpFAsTRn3TEkzqkytiOY0Dz0sq5cnd1dtNlk6sHLon4OvqaiJb28T0S/TdsBI3Sjsy+keJrw==", "dev": true }, "@types/d3-selection": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.6.tgz", - "integrity": "sha512-2ACr96USZVjXR9KMD9IWi1Epo4rSDKnUtYn6q2SPhYxykvXTw9vR77lkFNruXVg4i1tzQtBxeDMx0oNvJWbF1w==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.10.tgz", + "integrity": "sha512-cuHoUgS/V3hLdjJOLTT691+G2QoqAjCVLmr4kJXR4ha56w1Zdu8UUQ5TxLRqudgNjwXeQxKMq4j+lyf9sWuslg==", "dev": true }, "@types/d3-shape": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.2.tgz", - "integrity": "sha512-NN4CXr3qeOUNyK5WasVUV8NCSAx/CRVcwcb0BuuS1PiTqwIm6ABi1SyasLZ/vsVCFDArF+W4QiGzSry1eKYQ7w==", + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.6.tgz", + "integrity": "sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA==", "dev": true, "requires": { "@types/d3-path": "*" } }, "@types/d3-time": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.0.tgz", - "integrity": "sha512-sZLCdHvBUcNby1cB6Fd3ZBrABbjz3v1Vm90nysCQ6Vt7vd6e/h9Lt7SiJUoEX0l4Dzc7P5llKyhqSi1ycSf1Hg==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.3.tgz", + "integrity": "sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw==", "dev": true }, "@types/d3-time-format": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.0.tgz", - "integrity": "sha512-yjfBUe6DJBsDin2BMIulhSHmr5qNR5Pxs17+oW4DoVPyVIXZ+m6bs7j1UVKP08Emv6jRmYrYqxYzO63mQxy1rw==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", + "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==", "dev": true }, "@types/d3-timer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.0.tgz", - "integrity": "sha512-HNB/9GHqu7Fo8AQiugyJbv6ZxYz58wef0esl4Mv828w1ZKpAshw/uFWVDUcIB9KKFeFKoxS3cHY07FFgtTRZ1g==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", "dev": true }, "@types/d3-transition": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.4.tgz", - "integrity": "sha512-512a4uCOjUzsebydItSXsHrPeQblCVk8IKjqCUmrlvBWkkVh3donTTxmURDo1YPwIVDh5YVwCAO6gR4sgimCPQ==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.8.tgz", + "integrity": "sha512-ew63aJfQ/ms7QQ4X7pk5NxQ9fZH/z+i24ZfJ6tJSfqxJMrYLiK01EAs2/Rtw/JreGUsS3pLPNV644qXFGnoZNQ==", "dev": true, "requires": { "@types/d3-selection": "*" } }, "@types/d3-zoom": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.4.tgz", - "integrity": "sha512-cqkuY1ah9ZQre2POqjSLcM8g40UVya/qwEUrNYP2/rCVljbmqKCVcv+ebvwhlI5azIbSEL7m+os6n+WlYA43aA==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", + "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", "dev": true, "requires": { "@types/d3-interpolate": "*", "@types/d3-selection": "*" } }, + "@types/diff-match-patch": { + "version": "1.0.36", + "resolved": "https://registry.npmjs.org/@types/diff-match-patch/-/diff-match-patch-1.0.36.tgz", + "integrity": "sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg==", + "dev": true + }, "@types/elliptic": { "version": "6.4.14", "resolved": "https://registry.npmjs.org/@types/elliptic/-/elliptic-6.4.14.tgz", @@ -34664,9 +34674,9 @@ "dev": true }, "@types/geojson": { - "version": "7946.0.10", - "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.10.tgz", - "integrity": "sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA==", + "version": "7946.0.13", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.13.tgz", + "integrity": "sha512-bmrNrgKMOhM3WsafmbGmC+6dsF2Z308vLFsQ3a/bT8X8Sv5clVYpPars/UPq+sAaJP+5OoLAYgwbkS5QEJdLUQ==", "dev": true }, "@types/get-params": { @@ -34814,9 +34824,9 @@ } }, "@types/lodash": { - "version": "4.14.202", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.202.tgz", - "integrity": "sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==" + "version": "4.17.4", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.4.tgz", + "integrity": "sha512-wYCP26ZLxaT3R39kiN2+HcJ4kTd3U1waI/cY7ivWYqFP6pW3ZNpvi6Wd6PHZx7T/t8z0vlkXMg3QYLa7DZ/IJQ==" }, "@types/lodash.debounce": { "version": "4.0.9", @@ -34868,26 +34878,13 @@ } }, "@types/node-fetch": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.4.tgz", - "integrity": "sha512-1ZX9fcN4Rvkvgv4E6PAY5WXUFWFcRWxZa3EW83UjycOB9ljJCedb2CupIP4RZMEwF/M3eTcCihbBRgwtGbg5Rg==", + "version": "2.6.10", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.10.tgz", + "integrity": "sha512-PPpPK6F9ALFTn59Ka3BaL+qGuipRfxNE8qVgkp0bVixeiR2c2/L+IVOiBdu9JhhT22sWnQEp6YyHGI2b2+CMcA==", "dev": true, "requires": { "@types/node": "*", - "form-data": "^3.0.0" - }, - "dependencies": { - "form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - } + "form-data": "^4.0.0" } }, "@types/parse-json": { @@ -34947,19 +34944,10 @@ "@types/react": "*" } }, - "@types/redux-devtools-themes": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@types/redux-devtools-themes/-/redux-devtools-themes-1.0.3.tgz", - "integrity": "sha512-KqiQ2+6VTb1Yn02+ZNQsB0XcoJTu/W4AhnIUaeTnkVFie5YWMoeSpW4IOFYJ2/HJ+wi1kLw+PHDgjZ3t+M6IRw==", - "dev": true, - "requires": { - "@types/base16": "*" - } - }, "@types/responselike": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", - "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", + "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", "dev": true, "requires": { "@types/node": "*" @@ -35027,9 +35015,9 @@ "dev": true }, "@types/styled-components": { - "version": "5.1.26", - "resolved": "https://registry.npmjs.org/@types/styled-components/-/styled-components-5.1.26.tgz", - "integrity": "sha512-KuKJ9Z6xb93uJiIyxo/+ksS7yLjS1KzG6iv5i78dhVg/X3u5t1H7juRWqVmodIdz6wGVaIApo1u01kmFRdJHVw==", + "version": "5.1.34", + "resolved": "https://registry.npmjs.org/@types/styled-components/-/styled-components-5.1.34.tgz", + "integrity": "sha512-mmiVvwpYklFIv9E8qfxuPyIt/OuyIrn6gMOAMOFUO3WJfSrSE+sGUoa4PiZj77Ut7bKZpaa6o1fBKS/4TOEvnA==", "dev": true, "requires": { "@types/hoist-non-react-statics": "*", @@ -35969,13 +35957,13 @@ "dev": true }, "ag-auth": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ag-auth/-/ag-auth-2.0.0.tgz", - "integrity": "sha512-gk5BgWzijPUaCMDYFtuggIlUDWGMlMSOdF/tdQa5sM7Nh0KowohFh3P3sx06gegufKWzWF97dwP/7hLg3CkqWg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ag-auth/-/ag-auth-2.0.1.tgz", + "integrity": "sha512-PpIBdQiTIzp+ELJ8pPHVvxxCIUGXONPSecFgh34zyr/Z3vpqPmT/S21u6lzt1jGPulOTic4Wm9FpVGzC/BYHSQ==", "dev": true, "requires": { "jsonwebtoken": "^9.0.0", - "sc-errors": "^2.0.0" + "sc-errors": "^2.0.2" } }, "ag-channel": { @@ -35988,23 +35976,23 @@ } }, "ag-request": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/ag-request/-/ag-request-1.0.0.tgz", - "integrity": "sha512-2f7I0cQLMVyGAqjSewVMEFuAsJsIY6egdE16UHS636r+8c6Oevrv0j6SrOIXyRN6yuNT4PBuhiKmrhHbh9OvEg==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ag-request/-/ag-request-1.0.1.tgz", + "integrity": "sha512-3F4pDpLy9mxOXop7LoWE78J5g2jmiEJ0gJfzcECOsf/NaCfyeNmOdNLDVM5dS4Hvbi9T+HENL4DmXq5XSotPaA==", "dev": true, "requires": { - "sc-errors": "^2.0.0" + "sc-errors": "^2.0.2" } }, "ag-simple-broker": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ag-simple-broker/-/ag-simple-broker-5.0.0.tgz", - "integrity": "sha512-LMwfBdoNm+8ac/RkgW6z1mjIvy2cgEqWa9cJUD5sc5uiJkIn/kXhVRlPdfa/MJtxPivo9DRhKb9DlSp2gCv+Cg==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ag-simple-broker/-/ag-simple-broker-6.0.1.tgz", + "integrity": "sha512-pDlHotEoC9uV2Uk8DrR570QXMiUd9QYwJZXWDlBJZEbYTHzMJLEJDJStxmn7Kp4eT7SIGoPFuzELYZyMYNZ2Kw==", "dev": true, "requires": { "ag-channel": "^5.0.0", - "async-stream-emitter": "^4.0.0", - "stream-demux": "^8.0.0" + "async-stream-emitter": "^7.0.1", + "stream-demux": "^10.0.1" } }, "agent-base": { @@ -36379,12 +36367,12 @@ } }, "async-stream-emitter": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/async-stream-emitter/-/async-stream-emitter-4.1.0.tgz", - "integrity": "sha512-cfPZYjHkhCdHSR+eux71vOU8+8Xb23oLyxccAjwYHgOxDb3+qSDb2HV1Y0Hmu39vZlse2cm15CUShLiVYXHCmQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/async-stream-emitter/-/async-stream-emitter-7.0.1.tgz", + "integrity": "sha512-1bgA3iZ80rCBX2LocvsyZPy0QB3/xM+CsXBze2HDHLmshOqx2JlAANGq23djaJ48e9fpcKzTzS1QM0hAKKI0UQ==", "dev": true, "requires": { - "stream-demux": "^8.1.0" + "stream-demux": "^10.0.1" } }, "asynciterator.prototype": { @@ -36680,12 +36668,6 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, - "base16": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/base16/-/base16-1.0.0.tgz", - "integrity": "sha512-pNdYkNPiJUnEhnfXV56+sQy8+AaPcG3POZAUnwr4EeqCUZFz4u2PePbo3e5Gj4ziYPCWGUZT9RHisvJKnwFuBQ==", - "dev": true - }, "base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -36946,12 +36928,12 @@ } }, "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "requires": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" } }, "broadcast-channel": { @@ -37704,9 +37686,9 @@ } }, "clone-response": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", - "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", + "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", "dev": true, "requires": { "mimic-response": "^1.0.0" @@ -38358,16 +38340,6 @@ "postcss-value-parser": "^4.0.2" } }, - "css-vendor": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-2.0.8.tgz", - "integrity": "sha512-x9Aq0XTInxrkuFeHKbYC7zWY8ai7qJ04Kxd9MnvbC1uO5DagxoHQjm4JvG+vCdXOoFtCjbL2XSZfxmoYa9uQVQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.8.3", - "is-in-browser": "^1.0.2" - } - }, "css-what": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", @@ -38409,14 +38381,14 @@ } }, "csstype": { - "version": "3.0.11", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.11.tgz", - "integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==" + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, "d3": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/d3/-/d3-7.8.5.tgz", - "integrity": "sha512-JgoahDG51ncUfJu6wX/1vWQEqOflgXyl4MaHqlcSruTez7yhaRKR9i8VjjcQGeS2en/jnFivXuaIMnseMMt0XA==", + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", + "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", "dev": true, "requires": { "d3-array": "3", @@ -38740,12 +38712,29 @@ } }, "d3tooltip": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3tooltip/-/d3tooltip-3.0.0.tgz", - "integrity": "sha512-bQSxCU0D99KEvpHZjB5oiXToc98FxIHOAAOJRncZMsU3iUU18a2X//FgPnuI0K7wVjfKfgr4dXw5sEvTXSyu6Q==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3tooltip/-/d3tooltip-3.0.1.tgz", + "integrity": "sha512-y1CqKBZE/PUVvXzPeJrAxRh5pj6hzz8/oFdEpgGI0TbhFVhQ1xBv0wCFEz6BzZ94t+5Y+n1x+Q6rGMDtlzy7rA==", "dev": true, "requires": { - "@babel/runtime": "^7.20.6" + "@babel/runtime": "^7.23.2" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.7.tgz", + "integrity": "sha512-w06OXVOFso7LcbzMiDGt+3X7Rh7Ho8MmgPoWU3rarH+8upf+wSU/grlGbWzQyr3DkdN6ZeuMFjpdwW0Q+HxobA==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.14.0" + } + }, + "regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "dev": true + } } }, "damerau-levenshtein": { @@ -39345,9 +39334,9 @@ "dev": true }, "electron": { - "version": "25.9.0", - "resolved": "https://registry.npmjs.org/electron/-/electron-25.9.0.tgz", - "integrity": "sha512-wgscxf2ORHL/8mAQfy7l9rVDG//wrG9RUQndG508kCCMHRq9deFyZ4psOMzySheBRSfGMcFoRFYSlkAeZr8cFg==", + "version": "27.2.0", + "resolved": "https://registry.npmjs.org/electron/-/electron-27.2.0.tgz", + "integrity": "sha512-no/iMICVLI/5G0IqgKFbB89HDN88DWwKeRO+dPfJPkpJISdEX8Cx/sMEOFuuRa4VNInNe5CKCqRWExK5z3AdcQ==", "dev": true, "requires": { "@electron/get": "^2.0.0", @@ -39356,9 +39345,9 @@ }, "dependencies": { "@types/node": { - "version": "18.18.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.9.tgz", - "integrity": "sha512-0f5klcuImLnG4Qreu9hPj/rEfFq6YRc5n2mAjSsH+ec/mJL+3voBH0+8T7o8RpFjH7ovc+TRsL/c7OYIQsPTfQ==", + "version": "18.19.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.4.tgz", + "integrity": "sha512-xNzlUhzoHotIsnFoXmJB+yWmBvFZgKCI9TtPIEdYIMM1KWfwuY8zh7wvc1u1OAXlC7dlf6mZVx/s+Y5KfFz19A==", "dev": true, "requires": { "undici-types": "~5.26.4" @@ -39965,9 +39954,9 @@ } }, "eslint-config-prettier": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz", - "integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", + "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", "dev": true, "requires": {} }, @@ -40621,9 +40610,9 @@ } }, "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "requires": { "to-regex-range": "^5.0.1" @@ -41455,9 +41444,9 @@ "dev": true }, "graphql": { - "version": "16.8.0", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.8.0.tgz", - "integrity": "sha512-0oKGaR+y3qcS5mCu1vb7KG+a89vjn06C7Ihq/dDl3jA+A8B3TKomvi3CiEcVLJQGalbu8F52LxkOym7U5sSfbg==", + "version": "16.8.1", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.8.1.tgz", + "integrity": "sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw==", "dev": true }, "growly": { @@ -41728,9 +41717,9 @@ } }, "html-entities": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.4.0.tgz", - "integrity": "sha512-igBTJcNNNhvZFRtm8uA6xMY6xYleeDwn3PeBCkDz7tHttv4F2hsDI2aPgNERWzvRcNYHNT3ymRaQzllmXj4YsQ==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.5.2.tgz", + "integrity": "sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==", "dev": true }, "html-escaper": { @@ -41817,9 +41806,9 @@ } }, "html-webpack-plugin": { - "version": "5.5.3", - "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.5.3.tgz", - "integrity": "sha512-6YrDKTuqaP/TquFH7h4srYWsZx+x6k6+FbsTm0ziCwGHDP78Unr1r9F/H4+sGmMbX08GQcJ+K64x55b+7VM/jg==", + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.0.tgz", + "integrity": "sha512-iwaY4wzbe48AfKLZ/Cc8k0L+FKG6oSNRaZ8x5A/T/IVDGyXcbHncM9TdDa93wn0FsSm82FhTKW7f3vS61thXAw==", "dev": true, "requires": { "@types/html-minifier-terser": "^6.0.0", @@ -41974,12 +41963,6 @@ "integrity": "sha512-Tkv80jtvbnkK3mYWxPZePGFpQ/tT3HNSs/sasF9P2YfkMezDl3ON37YN6jUUI4eTg5LcyVynlb6r4eyvOmspvg==", "dev": true }, - "hyphenate-style-name": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz", - "integrity": "sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==", - "dev": true - }, "i18next": { "version": "23.11.0", "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.11.0.tgz", @@ -42177,9 +42160,9 @@ "dev": true }, "immutable": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.4.tgz", - "integrity": "sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA==", + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.6.tgz", + "integrity": "sha512-Ju0+lEMyzMVZarkTn/gqRpdqd5dOPaz1mCZ0SH3JV6iFw81PldE/PEB1hWVEA288HPt4WXW8O7AWxB10M+03QQ==", "dev": true }, "import-fresh": { @@ -42467,12 +42450,6 @@ "is-extglob": "^2.1.1" } }, - "is-in-browser": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz", - "integrity": "sha512-FeXIBgG/CPGd/WUxuEyvgGTEfwiG9Z4EKGxjNMRqviiIIfsmgrpnHLffEDdwUHqNva1VEW91o3xBT/m8Elgl9g==", - "dev": true - }, "is-inside-container": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", @@ -44576,168 +44553,6 @@ "verror": "1.10.0" } }, - "jss": { - "version": "10.10.0", - "resolved": "https://registry.npmjs.org/jss/-/jss-10.10.0.tgz", - "integrity": "sha512-cqsOTS7jqPsPMjtKYDUpdFC0AbhYFLTcuGRqymgmdJIeQ8cH7+AgX7YSgQy79wXloZq2VvATYxUOUQEvS1V/Zw==", - "dev": true, - "requires": { - "@babel/runtime": "^7.3.1", - "csstype": "^3.0.2", - "is-in-browser": "^1.1.3", - "tiny-warning": "^1.0.2" - } - }, - "jss-plugin-camel-case": { - "version": "10.10.0", - "resolved": "https://registry.npmjs.org/jss-plugin-camel-case/-/jss-plugin-camel-case-10.10.0.tgz", - "integrity": "sha512-z+HETfj5IYgFxh1wJnUAU8jByI48ED+v0fuTuhKrPR+pRBYS2EDwbusU8aFOpCdYhtRc9zhN+PJ7iNE8pAWyPw==", - "dev": true, - "requires": { - "@babel/runtime": "^7.3.1", - "hyphenate-style-name": "^1.0.3", - "jss": "10.10.0" - } - }, - "jss-plugin-compose": { - "version": "10.10.0", - "resolved": "https://registry.npmjs.org/jss-plugin-compose/-/jss-plugin-compose-10.10.0.tgz", - "integrity": "sha512-F5kgtWpI2XfZ3Z8eP78tZEYFdgTIbpA/TMuX3a8vwrNolYtN1N4qJR/Ob0LAsqIwCMLojtxN7c7Oo/+Vz6THow==", - "dev": true, - "requires": { - "@babel/runtime": "^7.3.1", - "jss": "10.10.0", - "tiny-warning": "^1.0.2" - } - }, - "jss-plugin-default-unit": { - "version": "10.10.0", - "resolved": "https://registry.npmjs.org/jss-plugin-default-unit/-/jss-plugin-default-unit-10.10.0.tgz", - "integrity": "sha512-SvpajxIECi4JDUbGLefvNckmI+c2VWmP43qnEy/0eiwzRUsafg5DVSIWSzZe4d2vFX1u9nRDP46WCFV/PXVBGQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.3.1", - "jss": "10.10.0" - } - }, - "jss-plugin-expand": { - "version": "10.10.0", - "resolved": "https://registry.npmjs.org/jss-plugin-expand/-/jss-plugin-expand-10.10.0.tgz", - "integrity": "sha512-ymT62W2OyDxBxr7A6JR87vVX9vTq2ep5jZLIdUSusfBIEENLdkkc0lL/Xaq8W9s3opUq7R0sZQpzRWELrfVYzA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.3.1", - "jss": "10.10.0" - } - }, - "jss-plugin-extend": { - "version": "10.10.0", - "resolved": "https://registry.npmjs.org/jss-plugin-extend/-/jss-plugin-extend-10.10.0.tgz", - "integrity": "sha512-sKYrcMfr4xxigmIwqTjxNcHwXJIfvhvjTNxF+Tbc1NmNdyspGW47Ey6sGH8BcQ4FFQhLXctpWCQSpDwdNmXSwg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.3.1", - "jss": "10.10.0", - "tiny-warning": "^1.0.2" - } - }, - "jss-plugin-global": { - "version": "10.10.0", - "resolved": "https://registry.npmjs.org/jss-plugin-global/-/jss-plugin-global-10.10.0.tgz", - "integrity": "sha512-icXEYbMufiNuWfuazLeN+BNJO16Ge88OcXU5ZDC2vLqElmMybA31Wi7lZ3lf+vgufRocvPj8443irhYRgWxP+A==", - "dev": true, - "requires": { - "@babel/runtime": "^7.3.1", - "jss": "10.10.0" - } - }, - "jss-plugin-nested": { - "version": "10.10.0", - "resolved": "https://registry.npmjs.org/jss-plugin-nested/-/jss-plugin-nested-10.10.0.tgz", - "integrity": "sha512-9R4JHxxGgiZhurDo3q7LdIiDEgtA1bTGzAbhSPyIOWb7ZubrjQe8acwhEQ6OEKydzpl8XHMtTnEwHXCARLYqYA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.3.1", - "jss": "10.10.0", - "tiny-warning": "^1.0.2" - } - }, - "jss-plugin-props-sort": { - "version": "10.10.0", - "resolved": "https://registry.npmjs.org/jss-plugin-props-sort/-/jss-plugin-props-sort-10.10.0.tgz", - "integrity": "sha512-5VNJvQJbnq/vRfje6uZLe/FyaOpzP/IH1LP+0fr88QamVrGJa0hpRRyAa0ea4U/3LcorJfBFVyC4yN2QC73lJg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.3.1", - "jss": "10.10.0" - } - }, - "jss-plugin-rule-value-function": { - "version": "10.10.0", - "resolved": "https://registry.npmjs.org/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.10.0.tgz", - "integrity": "sha512-uEFJFgaCtkXeIPgki8ICw3Y7VMkL9GEan6SqmT9tqpwM+/t+hxfMUdU4wQ0MtOiMNWhwnckBV0IebrKcZM9C0g==", - "dev": true, - "requires": { - "@babel/runtime": "^7.3.1", - "jss": "10.10.0", - "tiny-warning": "^1.0.2" - } - }, - "jss-plugin-rule-value-observable": { - "version": "10.10.0", - "resolved": "https://registry.npmjs.org/jss-plugin-rule-value-observable/-/jss-plugin-rule-value-observable-10.10.0.tgz", - "integrity": "sha512-ZLMaYrR3QE+vD7nl3oNXuj79VZl9Kp8/u6A1IbTPDcuOu8b56cFdWRZNZ0vNr8jHewooEeq2doy8Oxtymr2ZPA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.3.1", - "jss": "10.10.0", - "symbol-observable": "^1.2.0" - } - }, - "jss-plugin-template": { - "version": "10.10.0", - "resolved": "https://registry.npmjs.org/jss-plugin-template/-/jss-plugin-template-10.10.0.tgz", - "integrity": "sha512-ocXZBIOJOA+jISPdsgkTs8wwpK6UbsvtZK5JI7VUggTD6LWKbtoxUzadd2TpfF+lEtlhUmMsCkTRNkITdPKa6w==", - "dev": true, - "requires": { - "@babel/runtime": "^7.3.1", - "jss": "10.10.0", - "tiny-warning": "^1.0.2" - } - }, - "jss-plugin-vendor-prefixer": { - "version": "10.10.0", - "resolved": "https://registry.npmjs.org/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.10.0.tgz", - "integrity": "sha512-UY/41WumgjW8r1qMCO8l1ARg7NHnfRVWRhZ2E2m0DMYsr2DD91qIXLyNhiX83hHswR7Wm4D+oDYNC1zWCJWtqg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.3.1", - "css-vendor": "^2.0.8", - "jss": "10.10.0" - } - }, - "jss-preset-default": { - "version": "10.10.0", - "resolved": "https://registry.npmjs.org/jss-preset-default/-/jss-preset-default-10.10.0.tgz", - "integrity": "sha512-GL175Wt2FGhjE+f+Y3aWh+JioL06/QWFgZp53CbNNq6ZkVU0TDplD8Bxm9KnkotAYn3FlplNqoW5CjyLXcoJ7Q==", - "dev": true, - "requires": { - "@babel/runtime": "^7.3.1", - "jss": "10.10.0", - "jss-plugin-camel-case": "10.10.0", - "jss-plugin-compose": "10.10.0", - "jss-plugin-default-unit": "10.10.0", - "jss-plugin-expand": "10.10.0", - "jss-plugin-extend": "10.10.0", - "jss-plugin-global": "10.10.0", - "jss-plugin-nested": "10.10.0", - "jss-plugin-props-sort": "10.10.0", - "jss-plugin-rule-value-function": "10.10.0", - "jss-plugin-rule-value-observable": "10.10.0", - "jss-plugin-template": "10.10.0", - "jss-plugin-vendor-prefixer": "10.10.0" - } - }, "jsx-ast-utils": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz", @@ -44849,9 +44664,9 @@ "dev": true }, "knex": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/knex/-/knex-2.5.1.tgz", - "integrity": "sha512-z78DgGKUr4SE/6cm7ku+jHvFT0X97aERh/f0MUKAKgFnwCYBEW4TFBqtHWFYiJFid7fMrtpZ/gxJthvz5mEByA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/knex/-/knex-3.1.0.tgz", + "integrity": "sha512-GLoII6hR0c4ti243gMs5/1Rb3B+AjwMOfjYm97pu0FOQa7JH56hgBxYf5WK2525ceSbBY1cjeZ9yk99GPMB6Kw==", "dev": true, "requires": { "colorette": "2.0.19", @@ -44863,7 +44678,7 @@ "getopts": "2.3.0", "interpret": "^2.2.0", "lodash": "^4.17.21", - "pg-connection-string": "2.6.1", + "pg-connection-string": "2.6.2", "rechoir": "^0.8.0", "resolve-from": "^5.0.0", "tarn": "^3.0.2", @@ -45263,12 +45078,6 @@ "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" }, - "lodash.curry": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.curry/-/lodash.curry-4.1.1.tgz", - "integrity": "sha512-/u14pXGviLaweY5JI0IUzgzF2J6Ne8INyzAZjImcryjgkZ+ebruBxy2/JaOOkTqScddcYtakjhSaeemV8lR0tA==", - "dev": true - }, "lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -47027,9 +46836,9 @@ "dev": true }, "pg-connection-string": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.1.tgz", - "integrity": "sha512-w6ZzNu6oMmIzEAYVw+RLK0+nqHPt8K3ZnknKi+g48Ak2pr3dtljJW3o+D/n2zzCG07Zoe9VOX3aiKpj+BN0pjg==", + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.2.tgz", + "integrity": "sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA==", "dev": true }, "picocolors": { @@ -47605,38 +47414,23 @@ } }, "react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "requires": { "loose-envify": "^1.1.0" } }, "react-base16-styling": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/react-base16-styling/-/react-base16-styling-0.9.1.tgz", - "integrity": "sha512-1s0CY1zRBOQ5M3T61wetEpvQmsYSNtWEcdYzyZNxKa8t7oDvaOn9d21xrGezGAHFWLM7SHcktPuPTrvoqxSfKw==", + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/react-base16-styling/-/react-base16-styling-0.10.0.tgz", + "integrity": "sha512-H1k2eFB6M45OaiRru3PBXkuCcn2qNmx+gzLb4a9IPMR7tMH8oBRXU5jGbPDYG1Hz+82d88ED0vjR8BmqU3pQdg==", "dev": true, "requires": { - "@babel/runtime": "^7.16.7", - "@types/base16": "^1.0.2", - "@types/lodash": "^4.14.178", - "base16": "^1.0.0", - "color": "^3.2.1", - "csstype": "^3.0.10", - "lodash.curry": "^4.1.1" - }, - "dependencies": { - "color": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", - "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", - "dev": true, - "requires": { - "color-convert": "^1.9.3", - "color-string": "^1.6.0" - } - } + "@types/lodash": "^4.17.0", + "color": "^4.2.3", + "csstype": "^3.1.3", + "lodash-es": "^4.17.21" } }, "react-dom": { @@ -47700,9 +47494,9 @@ } }, "react-icons": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.11.0.tgz", - "integrity": "sha512-V+4khzYcE5EBk/BvcuYRq6V/osf11ODUM2J8hg2FDSswRrGvqiYUYPRy4OdrWaQOBj4NcpJfmHZLNaD+VH0TyA==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.12.0.tgz", + "integrity": "sha512-IBaDuHiShdZqmfc/TwHu6+d6k2ltNCf3AszxNmjJc1KUfXdEeRJOKyNvLmAHaarhzGmTSVygNdyu8/opXv2gaw==", "dev": true, "requires": {} }, @@ -47735,14 +47529,13 @@ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" }, "react-json-tree": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/react-json-tree/-/react-json-tree-0.18.0.tgz", - "integrity": "sha512-Qe6HKSXrr++n9Y31nkRJ3XvQMATISpqigH1vEKhLwB56+nk5thTP0ITThpjxY6ZG/ubpVq/aEHIcyLP/OPHxeA==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/react-json-tree/-/react-json-tree-0.19.0.tgz", + "integrity": "sha512-PqT1WRVcWP+RROsZPQfNEKIC1iM/ZMfY4g5jN6oDnXp5593PPRAYgoHcgYCDjflAHQMtxl8XGdlTwIBdEGUXvw==", "dev": true, "requires": { - "@babel/runtime": "^7.20.6", - "@types/lodash": "^4.14.191", - "react-base16-styling": "^0.9.1" + "@types/lodash": "^4.17.0", + "react-base16-styling": "^0.10.0" } }, "react-loading-skeleton": { @@ -47892,15 +47685,6 @@ "@babel/runtime": "^7.9.2" } }, - "redux-devtools-themes": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/redux-devtools-themes/-/redux-devtools-themes-1.0.0.tgz", - "integrity": "sha512-hBWqdZX+dioMWnTjf8+uSm0q1wCdYO4kU5gERzHcMMbu0Qg7JDR42TnJ6GHJ6r7k/tIpsCSygc9U0ehAtR24TQ==", - "dev": true, - "requires": { - "base16": "^1.0.0" - } - }, "redux-persist": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/redux-persist/-/redux-persist-6.0.0.tgz", @@ -48486,9 +48270,9 @@ } }, "sc-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/sc-errors/-/sc-errors-2.0.1.tgz", - "integrity": "sha512-JoVhq3Ud+3Ujv2SIG7W0XtjRHsrNgl6iXuHHsh0s+Kdt5NwI6N2EGAZD4iteitdDv68ENBkpjtSvN597/wxPSQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/sc-errors/-/sc-errors-2.0.3.tgz", + "integrity": "sha512-HNpClBWpo7zxLBnhH0U/FbC19Gl3OJlVyPxo9Q2eomfdWgYfd84uhqe0LRgybc+nSpcYjtF08+/dKPLugLMMeQ==", "dev": true }, "schema-utils": { @@ -48957,21 +48741,21 @@ "optional": true }, "socketcluster-server": { - "version": "17.4.1", - "resolved": "https://registry.npmjs.org/socketcluster-server/-/socketcluster-server-17.4.1.tgz", - "integrity": "sha512-ElKD9U7EncoWNGYOL+G6UAxuYmui1fnawpyhZIpFG/A/lDNGwHsQLNIpJXh1SB4BpoQLn2q4ewwnUK3Mse5mfA==", + "version": "19.0.1", + "resolved": "https://registry.npmjs.org/socketcluster-server/-/socketcluster-server-19.0.1.tgz", + "integrity": "sha512-IfMZxTkzvqOUExqiVxkxE2DjN/tap6WpbplatptsHKa58wfN6SdfcYCYeR3hlUBOx+cG09/hPxlN+R5tYZF0Mg==", "dev": true, "requires": { "ag-auth": "^2.0.0", - "ag-request": "^1.0.0", - "ag-simple-broker": "^5.0.0", - "async-stream-emitter": "^4.0.0", + "ag-request": "^1.0.1", + "ag-simple-broker": "^6.0.0", + "async-stream-emitter": "^7.0.1", "base64id": "^2.0.0", "clone-deep": "^4.0.1", - "sc-errors": "^2.0.1", + "sc-errors": "^2.0.3", "sc-formatter": "^4.0.0", - "stream-demux": "^8.0.0", - "writable-consumable-stream": "^2.0.0", + "stream-demux": "^10.0.1", + "writable-consumable-stream": "^4.1.0", "ws": "^8.9.0" }, "dependencies": { @@ -48982,9 +48766,9 @@ "dev": true }, "ws": { - "version": "8.14.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.1.tgz", - "integrity": "sha512-4OOseMUq8AzRBI/7SLMUwO+FEDnguetSk7KMb1sHwvF2w2Wv5Hoj0nlifx8vtGsftE/jWHojPy8sMMzYLJ2G/A==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", + "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", "dev": true, "requires": {} } @@ -49274,23 +49058,20 @@ } }, "stream-demux": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/stream-demux/-/stream-demux-8.1.0.tgz", - "integrity": "sha512-20vtOmAj2EVzQZKZVmfyio16u/3QOKSvg+0ldgZeS+m2FNI1vKFoqggamagsPCXufdZ1Tk8VvAM/HV/YUmRbSg==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/stream-demux/-/stream-demux-10.0.1.tgz", + "integrity": "sha512-QjTYLJWpZxZ6uL5R1JzgOzjvao8zDx78ec+uOjHNeVc/9TuasYLldoVrYARZeT1xI1hFYuiKf13IM8b4wamhHg==", "dev": true, "requires": { - "consumable-stream": "^2.0.0", - "writable-consumable-stream": "^3.0.1" + "consumable-stream": "^3.0.0", + "writable-consumable-stream": "^4.1.0" }, "dependencies": { - "writable-consumable-stream": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/writable-consumable-stream/-/writable-consumable-stream-3.0.1.tgz", - "integrity": "sha512-rAOJTA/sMgXD/X6eMwbQJe49w+Fnkdx3iV5oUzdmiZ7Bwx03khqUnAKIpzp/hbI8q2EP5NfjXgIXN0MsipfHeg==", - "dev": true, - "requires": { - "consumable-stream": "^2.0.0" - } + "consumable-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/consumable-stream/-/consumable-stream-3.0.0.tgz", + "integrity": "sha512-CnnsJ9OG9ouxAjt3pc63/DaerezRo/WudqU71pc5epaIUi7NHu2T4v+3f0nKbbCY7icS/TfQ1Satr9rwZ7Jwsg==", + "dev": true } } }, @@ -49589,12 +49370,6 @@ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true }, - "symbol-observable": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", - "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==", - "dev": true - }, "symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", @@ -49816,12 +49591,6 @@ "integrity": "sha512-Cc+OraorugtXNfs50hU9KS369rFXCfgGLpfCfvlc+Ud5u6VWmUQsOAa9HbTvheQdYnrdJqqv1e5oIqXppMYnSw==", "dev": true }, - "tiny-warning": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", - "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==", - "dev": true - }, "titleize": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/titleize/-/titleize-3.0.0.tgz", @@ -51563,12 +51332,20 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "writable-consumable-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/writable-consumable-stream/-/writable-consumable-stream-2.0.0.tgz", - "integrity": "sha512-SITambzxtPTFU/wR82h+zOKGBiEv5V8gC1mt8xvoE1/168ApEa8H+6s2UToYJo3VLL7sNYTaApKuPD+pZHMGJQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/writable-consumable-stream/-/writable-consumable-stream-4.1.0.tgz", + "integrity": "sha512-4cjCPd4Ayfbix0qqPCzMbnPPZKRh/cKeNCj05unybP3/sRkRAOxh7rSwbhxs3YB6G4/Z2p/2FRBEIQcTeB4jyw==", "dev": true, "requires": { - "consumable-stream": "^2.0.0" + "consumable-stream": "^3.0.0" + }, + "dependencies": { + "consumable-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/consumable-stream/-/consumable-stream-3.0.0.tgz", + "integrity": "sha512-CnnsJ9OG9ouxAjt3pc63/DaerezRo/WudqU71pc5epaIUi7NHu2T4v+3f0nKbbCY7icS/TfQ1Satr9rwZ7Jwsg==", + "dev": true + } } }, "write-file-atomic": { diff --git a/package.json b/package.json index 475187cac..25c5f45e0 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "Casper Wallet", "description": "Securely manage your CSPR tokens and interact with dapps with the self-custody wallet for the Casper blockchain.", - "version": "1.10.0", + "version": "1.11.0", "author": "MAKE LLC", "scripts": { "devtools:redux": "redux-devtools --hostname=localhost", @@ -106,7 +106,7 @@ "@babel/preset-react": "7.18.6", "@babel/preset-typescript": "^7.23.3", "@playwright/test": "^1.39.0", - "@redux-devtools/cli": "^3.0.1", + "@redux-devtools/cli": "^4.0.0", "@redux-devtools/remote": "^0.9.1", "@testing-library/dom": "9.3.4", "@testing-library/jest-dom": "^6.4.2", @@ -131,7 +131,7 @@ "css-loader": "6.8.1", "eslint": "8.49.0", "eslint-config-airbnb": "^19.0.4", - "eslint-config-prettier": "^8.3.0", + "eslint-config-prettier": "^9.1.0", "eslint-config-react-app": "^7.0.0", "eslint-import-resolver-typescript": "^3.6.1", "eslint-plugin-flowtype": "^8.0.3", @@ -143,7 +143,7 @@ "file-loader": "^6.2.0", "fs-extra": "11.1.1", "html-loader": "4.2.0", - "html-webpack-plugin": "^5.5.3", + "html-webpack-plugin": "^5.6.0", "husky": "8.0.2", "i18next-conv": "14.0.0", "jest": "29.3.1", diff --git a/scripts/build_all.sh b/scripts/build_all.sh index 2e5e60028..80f71e514 100755 --- a/scripts/build_all.sh +++ b/scripts/build_all.sh @@ -1,3 +1,3 @@ HASH=$(git rev-parse --short HEAD) -npm run build:chrome && npm run build:firefox && cd ./build && zip -r casper-wallet#$HASH.zip ./* && npm run build:src +npm run build:chrome && npm run build:firefox && cd ./build && zip -r casper-wallet-1.11.0.rc5#$HASH.zip ./* && npm run build:src diff --git a/src/apps/import-account-with-file/app-router.tsx b/src/apps/import-account-with-file/app-router.tsx index 710f4c748..0d81c423f 100644 --- a/src/apps/import-account-with-file/app-router.tsx +++ b/src/apps/import-account-with-file/app-router.tsx @@ -4,11 +4,10 @@ import { HashRouter, Route, Routes } from 'react-router-dom'; import { useUserActivityTracker } from '@hooks/use-user-activity-tracker'; import '@libs/i18n/i18n'; -import { HeaderPopup, LayoutWindow } from '@libs/layout'; import { ImportAccountWithFilePage } from './pages/import-account-with-file'; -import { ImportAccountWithFileFailureContentPage } from './pages/import-account-with-file-failure'; -import { ImportAccountWithFileSuccessContentPage } from './pages/import-account-with-file-success'; +import { ImportAccountWithFileFailurePage } from './pages/import-account-with-file-failure'; +import { ImportAccountWithFileSuccessPage } from './pages/import-account-with-file-success'; import { ImportAccountWithFileUploadPage } from './pages/import-account-with-file-upload'; import { RouterPath } from './router'; @@ -28,21 +27,11 @@ export function AppRouter() { /> } - renderContent={() => } - /> - } + element={} /> } - renderContent={() => } - /> - } + element={} /> diff --git a/src/apps/import-account-with-file/pages/import-account-with-file-failure/content.tsx b/src/apps/import-account-with-file/pages/import-account-with-file-failure/content.tsx new file mode 100644 index 000000000..e046ec40f --- /dev/null +++ b/src/apps/import-account-with-file/pages/import-account-with-file-failure/content.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +import { Trans, useTranslation } from 'react-i18next'; + +import { useTypedLocation } from '@import-account-with-file/router'; + +import { + ContentContainer, + IllustrationContainer, + ParagraphContainer, + SpacingSize +} from '@libs/layout'; +import { SvgIcon, Typography } from '@libs/ui/components'; + +export function ImportAccountWithFileFailureContentPage() { + const { t } = useTranslation(); + const location = useTypedLocation(); + const state = location.state; + + return ( + + + + + + + Something went wrong + + + + + {state?.importAccountStatusMessage + ? state.importAccountStatusMessage + : t( + ': We couldn’t import your account. Please confirm that you’re importing a file containing your secret key (not to be confused with your public key).' + )} + + + + ); +} diff --git a/src/apps/import-account-with-file/pages/import-account-with-file-failure/index.tsx b/src/apps/import-account-with-file/pages/import-account-with-file-failure/index.tsx index e69b48ffa..79831c7a1 100644 --- a/src/apps/import-account-with-file/pages/import-account-with-file-failure/index.tsx +++ b/src/apps/import-account-with-file/pages/import-account-with-file-failure/index.tsx @@ -1,60 +1,37 @@ import React from 'react'; import { Trans, useTranslation } from 'react-i18next'; -import { - RouterPath, - useTypedLocation, - useTypedNavigate -} from '@import-account-with-file/router'; +import { RouterPath, useTypedNavigate } from '@import-account-with-file/router'; import { closeCurrentWindow } from '@background/close-current-window'; import { - ContentContainer, - FooterButtonsAbsoluteContainer, - IllustrationContainer, - ParagraphContainer, - SpacingSize + FooterButtonsContainer, + HeaderPopup, + LayoutWindow } from '@libs/layout'; -import { Button, SvgIcon, Typography } from '@libs/ui/components'; +import { Button } from '@libs/ui/components'; + +import { ImportAccountWithFileFailureContentPage } from './content'; -export function ImportAccountWithFileFailureContentPage() { +export const ImportAccountWithFileFailurePage = () => { const { t } = useTranslation(); const navigate = useTypedNavigate(); - const location = useTypedLocation(); - const state = location.state; return ( - - - - - - - Something went wrong - - - - - {state?.importAccountStatusMessage - ? state.importAccountStatusMessage - : t( - ': We couldn’t import your account. Please confirm that you’re importing a file containing your secret key (not to be confused with your public key).' - )} - - - - - - - + } + renderContent={() => } + renderFooter={() => ( + + + + + )} + /> ); -} +}; diff --git a/src/apps/import-account-with-file/pages/import-account-with-file-success/content.tsx b/src/apps/import-account-with-file/pages/import-account-with-file-success/content.tsx new file mode 100644 index 000000000..0801d7f1e --- /dev/null +++ b/src/apps/import-account-with-file/pages/import-account-with-file-success/content.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import { Trans, useTranslation } from 'react-i18next'; + +import { + ContentContainer, + IllustrationContainer, + ParagraphContainer, + SpacingSize +} from '@libs/layout'; +import { SvgIcon, Typography } from '@libs/ui/components'; + +export function ImportAccountWithFileSuccessContentPage() { + const { t } = useTranslation(); + + return ( + + + + + + + Your account was successfully imported + + + + + + Imported accounts are distinguished by an ‘IMPORTED’ icon in the + account lists. + + + + + ); +} diff --git a/src/apps/import-account-with-file/pages/import-account-with-file-success/index.tsx b/src/apps/import-account-with-file/pages/import-account-with-file-success/index.tsx index 74ecc1f9e..dd7ad7576 100644 --- a/src/apps/import-account-with-file/pages/import-account-with-file-success/index.tsx +++ b/src/apps/import-account-with-file/pages/import-account-with-file-success/index.tsx @@ -4,44 +4,28 @@ import { Trans, useTranslation } from 'react-i18next'; import { closeCurrentWindow } from '@background/close-current-window'; import { - ContentContainer, - FooterButtonsAbsoluteContainer, - IllustrationContainer, - ParagraphContainer, - SpacingSize + FooterButtonsContainer, + HeaderPopup, + LayoutWindow } from '@libs/layout'; -import { Button, SvgIcon, Typography } from '@libs/ui/components'; +import { Button } from '@libs/ui/components'; -export function ImportAccountWithFileSuccessContentPage() { +import { ImportAccountWithFileSuccessContentPage } from './content'; + +export const ImportAccountWithFileSuccessPage = () => { const { t } = useTranslation(); return ( - - - - - - - Your account was successfully imported - - - - - - Imported accounts are distinguished by an ‘IMPORTED’ icon in the - account lists. - - - - - - - + } + renderContent={() => } + renderFooter={() => ( + + + + )} + /> ); -} +}; diff --git a/src/apps/onboarding/app-router.tsx b/src/apps/onboarding/app-router.tsx index e6d76fa3e..4438db7ae 100644 --- a/src/apps/onboarding/app-router.tsx +++ b/src/apps/onboarding/app-router.tsx @@ -16,6 +16,7 @@ import { CreateVaultPasswordPage } from '@onboarding/pages/create-vault-password import { OnboardingSuccessPage } from '@onboarding/pages/onboarding-success'; import { RecoverFromSecretPhrasePage } from '@onboarding/pages/recover-from-secret-phrase'; import { ResetWalletPage } from '@onboarding/pages/reset-wallet'; +import { SelectAccountsToRecoverPage } from '@onboarding/pages/select-accounts-to-recover'; import { UnlockWalletPage } from '@onboarding/pages/unlock-wallet'; import { WelcomePage } from '@onboarding/pages/welcome'; import { WriteDownSecretPhrasePage } from '@onboarding/pages/write-down-secret-phrase'; @@ -110,6 +111,10 @@ function AuthorizedUserRoutes({ path={RouterPath.RecoverFromSecretPhrase} element={} /> + } + /> - - Awesome, your secret phrase is confirmed! + + Step 6 + + + Awesome, your secret phrase is confirmed! + + diff --git a/src/apps/onboarding/pages/confirm-secret-phrase-success/index.tsx b/src/apps/onboarding/pages/confirm-secret-phrase-success/index.tsx index 747085eec..5e7731d42 100644 --- a/src/apps/onboarding/pages/confirm-secret-phrase-success/index.tsx +++ b/src/apps/onboarding/pages/confirm-secret-phrase-success/index.tsx @@ -30,7 +30,7 @@ export function ConfirmSecretPhraseSuccessPage() { )} renderContent={() => } renderFooter={() => ( - + diff --git a/src/apps/onboarding/pages/confirm-secret-phrase/content.tsx b/src/apps/onboarding/pages/confirm-secret-phrase/content.tsx index 1cb74f7be..0dbd19eb3 100644 --- a/src/apps/onboarding/pages/confirm-secret-phrase/content.tsx +++ b/src/apps/onboarding/pages/confirm-secret-phrase/content.tsx @@ -2,7 +2,12 @@ import React, { Dispatch, SetStateAction } from 'react'; import { Trans, useTranslation } from 'react-i18next'; import { SecretPhrase } from '@libs/crypto'; -import { TabPageContainer, TabTextContainer } from '@libs/layout'; +import { + SpacingSize, + TabPageContainer, + TabTextContainer, + VerticalSpaceContainer +} from '@libs/layout'; import { SecretPhraseWordsView, Typography, @@ -24,9 +29,14 @@ export function ConfirmSecretPhrasePageContent({ return ( - - Confirm your secret recovery phrase + + Step 5 + + + Confirm your secret recovery phrase + + diff --git a/src/apps/onboarding/pages/confirm-secret-phrase/index.tsx b/src/apps/onboarding/pages/confirm-secret-phrase/index.tsx index 23f26e9be..75a9a6896 100644 --- a/src/apps/onboarding/pages/confirm-secret-phrase/index.tsx +++ b/src/apps/onboarding/pages/confirm-secret-phrase/index.tsx @@ -80,7 +80,7 @@ export function ConfirmSecretPhrasePage({ /> )} renderFooter={() => ( - + diff --git a/src/apps/onboarding/pages/create-secret-phrase-confirmation/content.tsx b/src/apps/onboarding/pages/create-secret-phrase-confirmation/content.tsx index 82d2df0ad..6b23eb3d1 100644 --- a/src/apps/onboarding/pages/create-secret-phrase-confirmation/content.tsx +++ b/src/apps/onboarding/pages/create-secret-phrase-confirmation/content.tsx @@ -36,11 +36,16 @@ export function CreateSecretPhraseConfirmationPageContent() { return ( - - - Before we generate your secret recovery phrase, please remember - + + Step 3 + + + + Before we generate your secret recovery phrase, please remember + + + diff --git a/src/apps/onboarding/pages/create-secret-phrase-confirmation/index.tsx b/src/apps/onboarding/pages/create-secret-phrase-confirmation/index.tsx index cdff335b1..e41a14b93 100644 --- a/src/apps/onboarding/pages/create-secret-phrase-confirmation/index.tsx +++ b/src/apps/onboarding/pages/create-secret-phrase-confirmation/index.tsx @@ -35,6 +35,7 @@ export function CreateSecretPhraseConfirmationPage({ return ( ( @@ -43,7 +44,7 @@ export function CreateSecretPhraseConfirmationPage({ )} renderContent={() => } renderFooter={() => ( - + setIsChecked(currentValue => !currentValue)} diff --git a/src/apps/onboarding/pages/create-secret-phrase/content.tsx b/src/apps/onboarding/pages/create-secret-phrase/content.tsx index e42557b30..8705ead5c 100644 --- a/src/apps/onboarding/pages/create-secret-phrase/content.tsx +++ b/src/apps/onboarding/pages/create-secret-phrase/content.tsx @@ -1,7 +1,12 @@ import React from 'react'; import { Trans, useTranslation } from 'react-i18next'; -import { TabPageContainer, TabTextContainer } from '@libs/layout'; +import { + SpacingSize, + TabPageContainer, + TabTextContainer, + VerticalSpaceContainer +} from '@libs/layout'; import { Typography } from '@libs/ui/components'; export function CreateSecretPhraseContent() { @@ -9,9 +14,14 @@ export function CreateSecretPhraseContent() { return ( - - Create secret recovery phrase + + Step 2 + + + Create secret recovery phrase + + diff --git a/src/apps/onboarding/pages/create-vault-password/content.tsx b/src/apps/onboarding/pages/create-vault-password/content.tsx index 954536df2..a8edef859 100644 --- a/src/apps/onboarding/pages/create-vault-password/content.tsx +++ b/src/apps/onboarding/pages/create-vault-password/content.tsx @@ -1,7 +1,12 @@ import React from 'react'; import { Trans, useTranslation } from 'react-i18next'; -import { TabPageContainer, TabTextContainer } from '@libs/layout'; +import { + SpacingSize, + TabPageContainer, + TabTextContainer, + VerticalSpaceContainer +} from '@libs/layout'; import { Typography } from '@libs/ui/components'; import { minPasswordLength } from '@libs/ui/forms/form-validation-rules'; @@ -16,9 +21,14 @@ export function CreateVaultPasswordPageContent({ return ( - - Create password + + Step 1 + + + Create password + + diff --git a/src/apps/onboarding/pages/create-vault-password/index.tsx b/src/apps/onboarding/pages/create-vault-password/index.tsx index 79814129e..efbd80f11 100644 --- a/src/apps/onboarding/pages/create-vault-password/index.tsx +++ b/src/apps/onboarding/pages/create-vault-password/index.tsx @@ -110,7 +110,7 @@ export function CreateVaultPasswordPage({ )} renderFooter={() => ( - + setIsChecked(currentValue => !currentValue)} diff --git a/src/apps/onboarding/pages/onboarding-success/content.tsx b/src/apps/onboarding/pages/onboarding-success/content.tsx index d6343d8b1..8fd99934e 100644 --- a/src/apps/onboarding/pages/onboarding-success/content.tsx +++ b/src/apps/onboarding/pages/onboarding-success/content.tsx @@ -1,14 +1,9 @@ import React from 'react'; import { Trans, useTranslation } from 'react-i18next'; -import styled from 'styled-components'; import { TabPageContainer, TabTextContainer } from '@libs/layout'; import { Link, SvgIcon, Typography } from '@libs/ui/components'; -const TipContainer = styled.div` - margin-top: 12px; -`; - export function OnboardingSuccessPageContent() { const { t } = useTranslation(); @@ -20,7 +15,7 @@ export function OnboardingSuccessPageContent() { height={120} /> - + Congrats! Your Casper Wallet is set up and ready to go @@ -36,7 +31,7 @@ export function OnboardingSuccessPageContent() { - + Tip: If this is your first time using a cryptocurrency web wallet we @@ -51,7 +46,7 @@ export function OnboardingSuccessPageContent() { . - + ); } diff --git a/src/apps/onboarding/pages/recover-from-secret-phrase/content.tsx b/src/apps/onboarding/pages/recover-from-secret-phrase/content.tsx index b5699ebdf..deb006237 100644 --- a/src/apps/onboarding/pages/recover-from-secret-phrase/content.tsx +++ b/src/apps/onboarding/pages/recover-from-secret-phrase/content.tsx @@ -4,8 +4,10 @@ import { Trans, useTranslation } from 'react-i18next'; import { InputsContainer, + SpacingSize, TabPageContainer, - TabTextContainer + TabTextContainer, + VerticalSpaceContainer } from '@libs/layout'; import { FormField, @@ -27,9 +29,14 @@ export function RecoverFromSecretPhrasePageContent({ const { t } = useTranslation(); return ( - - Please enter your secret recovery phrase + + Step 3 + + + Please enter your secret recovery phrase + + diff --git a/src/apps/onboarding/pages/recover-from-secret-phrase/index.tsx b/src/apps/onboarding/pages/recover-from-secret-phrase/index.tsx index 1f3b14917..962152a03 100644 --- a/src/apps/onboarding/pages/recover-from-secret-phrase/index.tsx +++ b/src/apps/onboarding/pages/recover-from-secret-phrase/index.tsx @@ -4,10 +4,6 @@ import { Trans, useTranslation } from 'react-i18next'; import { Stepper } from '@onboarding/components/stepper'; import { RouterPath } from '@onboarding/router'; import { useTypedNavigate } from '@onboarding/router/use-typed-navigate'; -import { closeActiveTab } from '@onboarding/utils/close-active-tab'; - -import { initVault } from '@background/redux/sagas/actions'; -import { dispatchToMainStore } from '@background/redux/utils'; import { validateSecretPhrase } from '@libs/crypto'; import { @@ -41,8 +37,11 @@ export function RecoverFromSecretPhrasePage() { if (!validateSecretPhrase(secretPhrase)) { throw Error('Invalid secret phrase.'); } - dispatchToMainStore(initVault({ secretPhrase })); - closeActiveTab(); + navigate(RouterPath.SelectAccountsToRecover, { + state: { + secretPhrase + } + }); } catch (err) { console.error(err); navigate( @@ -67,13 +66,14 @@ export function RecoverFromSecretPhrasePage() {
( navigate(RouterPath.CreateSecretPhrase)} /> - + )} renderContent={() => ( @@ -85,7 +85,7 @@ export function RecoverFromSecretPhrasePage() { renderFooter={() => ( )} diff --git a/src/apps/onboarding/pages/reset-wallet/content.tsx b/src/apps/onboarding/pages/reset-wallet/content.tsx index 5d0990d76..85c2b323d 100644 --- a/src/apps/onboarding/pages/reset-wallet/content.tsx +++ b/src/apps/onboarding/pages/reset-wallet/content.tsx @@ -11,7 +11,7 @@ export function ResetWalletPageContent() { return ( - + Are you sure you want to start again? diff --git a/src/apps/onboarding/pages/select-accounts-to-recover/content.tsx b/src/apps/onboarding/pages/select-accounts-to-recover/content.tsx new file mode 100644 index 000000000..d80b0b79b --- /dev/null +++ b/src/apps/onboarding/pages/select-accounts-to-recover/content.tsx @@ -0,0 +1,106 @@ +import { Player } from '@lottiefiles/react-lottie-player'; +import React, { SetStateAction } from 'react'; +import { Trans, useTranslation } from 'react-i18next'; +import styled from 'styled-components'; + +import { useIsDarkMode } from '@hooks/use-is-dark-mode'; + +import spinnerDarkModeAnimation from '@libs/animations/spinner_dark_mode.json'; +import spinnerLightModeAnimation from '@libs/animations/spinner_light_mode.json'; +import { + CenteredFlexColumn, + SpacingSize, + TabPageContainer, + VerticalSpaceContainer +} from '@libs/layout'; +import { AccountListRows } from '@libs/types/account'; +import { + DynamicAccountsListWithSelect, + Tile, + Typography +} from '@libs/ui/components'; + +const AnimationContainer = styled(CenteredFlexColumn)` + padding: 106px 16px; +`; + +interface SelectAccountsToRecoverContentProps { + isLoading: boolean; + derivedAccountsWithBalance: AccountListRows[]; + isLoadingMore: boolean; + onLoadMore: () => void; + maxItemsToRender: number; + setSelectedAccounts: React.Dispatch>; + selectedAccounts: AccountListRows[]; + setIsButtonDisabled: React.Dispatch>; +} + +export const SelectAccountsToRecoverContent = ({ + isLoading, + isLoadingMore, + onLoadMore, + derivedAccountsWithBalance, + maxItemsToRender, + setSelectedAccounts, + selectedAccounts, + setIsButtonDisabled +}: SelectAccountsToRecoverContentProps) => { + const { t } = useTranslation(); + const isDarkMode = useIsDarkMode(); + + return ( + + + Step 4 + + + + Select accounts to recover + + + + {isLoading ? ( + + + + + + + Just a moment + + + Your accounts will be here shortly. + + + + + + ) : ( + + )} + + ); +}; diff --git a/src/apps/onboarding/pages/select-accounts-to-recover/index.tsx b/src/apps/onboarding/pages/select-accounts-to-recover/index.tsx new file mode 100644 index 000000000..ea1250ca7 --- /dev/null +++ b/src/apps/onboarding/pages/select-accounts-to-recover/index.tsx @@ -0,0 +1,182 @@ +import React, { useEffect, useState } from 'react'; +import { Trans, useTranslation } from 'react-i18next'; +import { useSelector } from 'react-redux'; + +import { isEqualCaseInsensitive } from '@src/utils'; + +import { Stepper } from '@onboarding/components/stepper'; +import { SelectAccountsToRecoverContent } from '@onboarding/pages/select-accounts-to-recover/content'; +import { getKeyPairList } from '@onboarding/pages/select-accounts-to-recover/utils'; +import { + RouterPath, + useTypedLocation, + useTypedNavigate +} from '@onboarding/router'; +import { closeActiveTab } from '@onboarding/utils/close-active-tab'; + +import { recoverVault } from '@background/redux/sagas/actions'; +import { selectApiConfigBasedOnActiveNetwork } from '@background/redux/settings/selectors'; +import { dispatchToMainStore } from '@background/redux/utils'; + +import { getAccountHashFromPublicKey } from '@libs/entities/Account'; +import { + HeaderSubmenuBarNavLink, + LayoutTab, + TabFooterContainer, + TabHeaderContainer +} from '@libs/layout'; +import { dispatchFetchAccountBalances } from '@libs/services/balance-service'; +import { Account, AccountListRows, KeyPair } from '@libs/types/account'; +import { Button } from '@libs/ui/components'; + +export const SelectAccountsToRecoverPage = () => { + const [derivedAccounts, setDerivedAccounts] = useState([]); + const [derivedAccountsWithBalance, setDerivedAccountsWithBalance] = useState< + AccountListRows[] + >([]); + const [selectedAccounts, setSelectedAccounts] = useState( + [] + ); + + const [isLoading, setIsLoading] = useState(true); + const [isLoadingMore, setIsLoadingMore] = useState(false); + const [maxItemsToRender, setMaxItemsToRender] = useState(5); + const [isButtonDisabled, setIsButtonDisabled] = useState(true); + + const navigate = useTypedNavigate(); + const { t } = useTranslation(); + const location = useTypedLocation(); + + const { casperWalletApiUrl } = useSelector( + selectApiConfigBasedOnActiveNetwork + ); + + useEffect(() => { + if (location.state?.secretPhrase) { + const keyPairs = getKeyPairList({ + size: 5, + offset: 0, + secretPhrase: location.state?.secretPhrase + }); + + setDerivedAccounts(prevState => [...prevState, ...keyPairs]); + } + }, [location.state?.secretPhrase, setDerivedAccounts]); + + useEffect(() => { + if (!derivedAccounts.length) return; + + const hashes = derivedAccounts.reduce( + (previousValue, currentValue, currentIndex) => { + const hash = getAccountHashFromPublicKey(currentValue.publicKey); + + return derivedAccounts.length === currentIndex + 1 + ? previousValue + `${hash}` + : previousValue + `${hash},`; + }, + '' + ); + + dispatchFetchAccountBalances(hashes) + .then(({ payload }) => { + if ('data' in payload) { + const derivedAccountsWithBalance: AccountListRows[] = + derivedAccounts.map((account, index) => { + const accountWithBalance = payload.data.find(ac => + isEqualCaseInsensitive(ac.public_key, account.publicKey) + ); + + return { + ...account, + id: account.publicKey, + hidden: false, + derivationIndex: index, + name: '', + balance: { + liquidMotes: `${accountWithBalance?.balance ?? '0'}` + } + }; + }); + + setDerivedAccountsWithBalance(derivedAccountsWithBalance); + } + }) + .finally(() => { + setIsLoading(false); + setIsLoadingMore(false); + }); + }, [derivedAccounts, casperWalletApiUrl]); + + const onLoadMore = () => { + try { + setIsLoadingMore(true); + const keyPairs = getKeyPairList({ + size: 5, + offset: derivedAccounts.length, + secretPhrase: location.state?.secretPhrase + }); + + setDerivedAccounts(prevState => [...prevState, ...keyPairs]); + setMaxItemsToRender(prevState => prevState + 5); + } catch (e) { + setIsLoadingMore(false); + } + }; + + const onSubmit = () => { + if (!location.state?.secretPhrase) return; + + const accounts: Account[] = selectedAccounts.map(account => ({ + name: account.name, + publicKey: account.publicKey, + secretKey: account.secretKey, + hidden: account.hidden, + derivationIndex: account.derivationIndex + })); + + dispatchToMainStore( + recoverVault({ + secretPhrase: location.state.secretPhrase, + accounts + }) + ).finally(closeActiveTab); + }; + + return ( + ( + + navigate(RouterPath.CreateSecretPhrase)} + /> + + + )} + renderContent={() => ( + + )} + renderFooter={() => ( + + + + )} + /> + ); +}; diff --git a/src/apps/onboarding/pages/select-accounts-to-recover/utils.ts b/src/apps/onboarding/pages/select-accounts-to-recover/utils.ts new file mode 100644 index 000000000..24d3b39cf --- /dev/null +++ b/src/apps/onboarding/pages/select-accounts-to-recover/utils.ts @@ -0,0 +1,24 @@ +import { deriveKeyPair } from '@libs/crypto'; +import { KeyPair } from '@libs/types/account'; + +export interface KeyPairOptions { + size: number; + offset: number; + secretPhrase: string[]; +} + +export const getKeyPairList = ({ + size, + offset, + secretPhrase +}: KeyPairOptions) => { + const keyPairs: KeyPair[] = []; + + for (let i = 0; i < size; i++) { + const keyPair = deriveKeyPair(secretPhrase, offset + i); + + keyPairs.push(keyPair); + } + + return keyPairs; +}; diff --git a/src/apps/onboarding/pages/unlock-wallet/content.tsx b/src/apps/onboarding/pages/unlock-wallet/content.tsx index f83f72280..3fdc59f77 100644 --- a/src/apps/onboarding/pages/unlock-wallet/content.tsx +++ b/src/apps/onboarding/pages/unlock-wallet/content.tsx @@ -19,15 +19,14 @@ import { import { UnlockWalletFormValues } from '@libs/ui/forms/unlock-wallet'; // Design of this page is temporary. Should be changed after it will be done in Figma -const TabPageContainer = styled.div` - padding-top: 24px; -`; +const TabPageContainer = styled.div``; const IllustrationContainer = styled(TabPageContainerBase)``; // It's need for good reading const GrayBackgroundContainer = styled(TabPageContainerBase)` background-color: ${({ theme }) => theme.color.backgroundSecondary}; + padding-bottom: 28px; `; interface LoginPageContentProps { diff --git a/src/apps/onboarding/pages/unlock-wallet/index.tsx b/src/apps/onboarding/pages/unlock-wallet/index.tsx index 2e1e6bdce..c33c9b27f 100644 --- a/src/apps/onboarding/pages/unlock-wallet/index.tsx +++ b/src/apps/onboarding/pages/unlock-wallet/index.tsx @@ -59,6 +59,7 @@ export function UnlockWalletPage({ saveIsLoggedIn }: UnlockWalletPageProps) { return ( ( - - + + Ready to create your new wallet? - + diff --git a/src/apps/onboarding/pages/write-down-secret-phrase/content.tsx b/src/apps/onboarding/pages/write-down-secret-phrase/content.tsx index 9099e306d..cfbfa61d0 100644 --- a/src/apps/onboarding/pages/write-down-secret-phrase/content.tsx +++ b/src/apps/onboarding/pages/write-down-secret-phrase/content.tsx @@ -2,7 +2,12 @@ import React from 'react'; import { Trans, useTranslation } from 'react-i18next'; import { SecretPhrase } from '@libs/crypto'; -import { TabPageContainer, TabTextContainer } from '@libs/layout'; +import { + SpacingSize, + TabPageContainer, + TabTextContainer, + VerticalSpaceContainer +} from '@libs/layout'; import { CopySecretPhraseBar, SecretPhraseWordsView, @@ -20,9 +25,14 @@ export function WriteDownSecretPhrasePageContent({ return ( - - Write down your secret recovery phrase + + Step 4 + + + Write down your secret recovery phrase + + diff --git a/src/apps/onboarding/pages/write-down-secret-phrase/index.tsx b/src/apps/onboarding/pages/write-down-secret-phrase/index.tsx index 8ae391daf..ce716f559 100644 --- a/src/apps/onboarding/pages/write-down-secret-phrase/index.tsx +++ b/src/apps/onboarding/pages/write-down-secret-phrase/index.tsx @@ -35,6 +35,7 @@ export function WriteDownSecretPhrasePage({ return ( ( @@ -43,7 +44,7 @@ export function WriteDownSecretPhrasePage({ )} renderContent={() => } renderFooter={() => ( - + setIsChecked(currentValue => !currentValue)} diff --git a/src/apps/onboarding/router/paths.ts b/src/apps/onboarding/router/paths.ts index 217457804..727522a96 100644 --- a/src/apps/onboarding/router/paths.ts +++ b/src/apps/onboarding/router/paths.ts @@ -9,5 +9,6 @@ export enum RouterPath { ConfirmSecretPhrase = '/confirm-secret-phrase', ConfirmSecretPhraseSuccess = '/confirm-secret-phrase-success', OnboardingSuccess = '/onboarding-success', - ResetWallet = '/reset-wallet' + ResetWallet = '/reset-wallet', + SelectAccountsToRecover = '/select-accounts-to-recover' } diff --git a/src/apps/onboarding/router/types.ts b/src/apps/onboarding/router/types.ts index 5518388f2..67a077b5e 100644 --- a/src/apps/onboarding/router/types.ts +++ b/src/apps/onboarding/router/types.ts @@ -1,3 +1,5 @@ import { ErrorLocationState } from '@libs/layout'; -export interface LocationState extends ErrorLocationState {} +export interface LocationState extends ErrorLocationState { + secretPhrase?: string[]; +} diff --git a/src/apps/popup/app-router.tsx b/src/apps/popup/app-router.tsx index 8cd0a67d0..7e67ea351 100644 --- a/src/apps/popup/app-router.tsx +++ b/src/apps/popup/app-router.tsx @@ -22,11 +22,11 @@ import { ImportAccountFromLedgerPage } from '@popup/pages/import-account-from-le import { ImportAccountFromTorusPage } from '@popup/pages/import-account-from-torus'; import { NavigationMenuPageContent } from '@popup/pages/navigation-menu'; import { NftDetailsPage } from '@popup/pages/nft-details'; -import { NoConnectedAccountPageContent } from '@popup/pages/no-connected-account'; +import { NoConnectedAccountPage } from '@popup/pages/no-connected-account'; import { RateAppPage } from '@popup/pages/rate-app'; import { ReceivePage } from '@popup/pages/receive'; -import { RemoveAccountPageContent } from '@popup/pages/remove-account'; -import { RenameAccountPageContent } from '@popup/pages/rename-account'; +import { RemoveAccountPage } from '@popup/pages/remove-account'; +import { RenameAccountPage } from '@popup/pages/rename-account'; import { SignWithLedgerInNewWindowPage } from '@popup/pages/sign-with-ledger-in-new-window'; import { StakesPage } from '@popup/pages/stakes'; import { TimeoutPageContent } from '@popup/pages/timeout'; @@ -134,52 +134,11 @@ function AppRoutes() { /> } /> - ( - ( - - )} - /> - )} - renderContent={() => } - /> - } - /> - ( - ( - - )} - /> - )} - renderContent={() => } - /> - } - /> + } /> + } /> ( - - )} - renderContent={() => } - /> - } + element={} /> ( - + + + + } + placeholder={t('Search')} + {...currencySearchRegister('currencySearch')} + /> + - + )} placement="fullBottom" children={() => ( diff --git a/src/apps/popup/pages/buy-cspr/country.tsx b/src/apps/popup/pages/buy-cspr/country.tsx index f02d848c7..7bf564501 100644 --- a/src/apps/popup/pages/buy-cspr/country.tsx +++ b/src/apps/popup/pages/buy-cspr/country.tsx @@ -7,21 +7,24 @@ import { selectAccountBalance } from '@background/redux/account-info/selectors'; import { ContentContainer, + InputsContainer, ParagraphContainer, SpacingSize } from '@libs/layout'; import { ResponseCountryPropsWithId } from '@libs/services/buy-cspr-service/types'; import { ActiveAccountPlate, + Input, List, Modal, + ModalSwitcher, + SvgIcon, Typography } from '@libs/ui/components'; import { motesToCSPR } from '@libs/ui/utils'; import { CountryRow } from './components/country-row'; import { ListRow } from './components/list-row'; -import { Switcher } from './components/switcher'; import { sortCountries } from './utils'; interface CountryProps { @@ -82,12 +85,14 @@ export const Country = ({ ( - + + + } + placeholder={t('Search')} + {...register('countryNameSearch')} + /> + - + )} placement="fullBottom" children={() => ( diff --git a/src/apps/popup/pages/change-password/index.tsx b/src/apps/popup/pages/change-password/index.tsx index 9b70db059..d36279738 100644 --- a/src/apps/popup/pages/change-password/index.tsx +++ b/src/apps/popup/pages/change-password/index.tsx @@ -1,12 +1,17 @@ -import React from 'react'; +import React, { useCallback, useState } from 'react'; import { useWatch } from 'react-hook-form'; import { Trans, useTranslation } from 'react-i18next'; +import { useSelector } from 'react-redux'; import { ChangePasswordPageContent } from '@popup/pages/change-password/content'; +import { PasswordProtectionPage } from '@popup/pages/password-protection-page'; import { RouterPath, useTypedNavigate } from '@popup/router'; -import { changePassword } from '@background/redux/sagas/actions'; +import { keysUpdated } from '@background/redux/keys/actions'; +import { encryptionKeyHashCreated } from '@background/redux/session/actions'; import { dispatchToMainStore } from '@background/redux/utils'; +import { vaultCipherCreated } from '@background/redux/vault-cipher/actions'; +import { selectVault } from '@background/redux/vault/selectors'; import { FooterButtonsContainer, @@ -21,10 +26,25 @@ import { } from '@libs/ui/forms/create-password'; import { calculateSubmitButtonDisabled } from '@libs/ui/forms/get-submit-button-state-from-validation'; +interface CreatePasswordWorkerMessageEvent extends MessageEvent { + data: { + passwordHash: string; + passwordSaltHash: string; + newEncryptionKeyHash: string; + keyDerivationSaltHash: string; + newVaultCipher: string; + }; +} + export const ChangePasswordPage = () => { + const [isPasswordConfirmed, setIsPasswordConfirmed] = + useState(false); + const { t } = useTranslation(); const navigate = useTypedNavigate(); + const vault = useSelector(selectVault); + const { register, handleSubmit, @@ -37,15 +57,65 @@ export const ChangePasswordPage = () => { name: 'password' }); + const setPasswordConfirmed = useCallback(() => { + setIsPasswordConfirmed(true); + }, []); + const isSubmitButtonDisabled = calculateSubmitButtonDisabled({ isDirty }); const onSubmit = (data: CreatePasswordFormValues) => { - dispatchToMainStore(changePassword({ password: data.password })); + const worker = new Worker( + new URL('@background/workers/create-password-worker.ts', import.meta.url) + ); + + worker.postMessage({ + password: data.password, + vault + }); + + worker.onmessage = (event: CreatePasswordWorkerMessageEvent) => { + const { + passwordHash, + passwordSaltHash, + newEncryptionKeyHash, + keyDerivationSaltHash, + newVaultCipher + } = event.data; + + dispatchToMainStore( + keysUpdated({ + passwordHash, + passwordSaltHash, + keyDerivationSaltHash + }) + ); + + dispatchToMainStore( + encryptionKeyHashCreated({ encryptionKeyHash: newEncryptionKeyHash }) + ); + + dispatchToMainStore( + vaultCipherCreated({ + vaultCipher: newVaultCipher + }) + ); + }; + + worker.onerror = error => { + console.error(error); + }; + navigate(RouterPath.Home); }; + if (!isPasswordConfirmed) { + return ( + + ); + } + return ( { const newName = name.trim(); diff --git a/src/apps/popup/pages/home/components/more-buttons-modal/modal-buttons.tsx b/src/apps/popup/pages/home/components/more-buttons-modal/modal-buttons.tsx index e7e7fed51..dec88451e 100644 --- a/src/apps/popup/pages/home/components/more-buttons-modal/modal-buttons.tsx +++ b/src/apps/popup/pages/home/components/more-buttons-modal/modal-buttons.tsx @@ -49,19 +49,7 @@ export const ModalButtons = () => { )} - navigate( - casperToken?.id - ? RouterPath.Transfer.replace( - ':tokenContractPackageHash', - casperToken.id - ).replace( - ':tokenContractHash', - casperToken.contractHash || 'null' - ) - : RouterPath.TransferNoParams - ) - } + onClick={() => navigate(RouterPath.Transfer)} > - - + ( + + )} + renderContent={() => } + renderFooter={() => ( + + + + )} + /> ); -} +}; diff --git a/src/apps/popup/pages/password-protection-page/index.tsx b/src/apps/popup/pages/password-protection-page/index.tsx index beaa380a1..e6d2769e9 100644 --- a/src/apps/popup/pages/password-protection-page/index.tsx +++ b/src/apps/popup/pages/password-protection-page/index.tsx @@ -1,12 +1,25 @@ -import React from 'react'; +import * as Yup from 'yup'; +import React, { useState } from 'react'; +import { useForm } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; import { useSelector } from 'react-redux'; +import { AnyObject } from 'yup/es/types'; + +import { + ERROR_DISPLAYED_BEFORE_ATTEMPT_IS_DECREMENTED, + LOGIN_RETRY_ATTEMPTS_LIMIT +} from '@src/constants'; +import { getErrorMessageForIncorrectPassword } from '@src/utils'; import { selectPasswordHash, selectPasswordSaltHash } from '@background/redux/keys/selectors'; -import { loginRetryCountReseted } from '@background/redux/login-retry-count/actions'; +import { + loginRetryCountIncremented, + loginRetryCountReseted +} from '@background/redux/login-retry-count/actions'; +import { selectLoginRetryCount } from '@background/redux/login-retry-count/selectors'; import { dispatchToMainStore } from '@background/redux/utils'; import { @@ -17,24 +30,40 @@ import { UnlockProtectedPageContent } from '@libs/layout'; import { Button } from '@libs/ui/components'; -import { calculateSubmitButtonDisabled } from '@libs/ui/forms/get-submit-button-state-from-validation'; -import { useUnlockWalletForm } from '@libs/ui/forms/unlock-wallet'; interface BackupSecretPhrasePasswordPageType { - setPasswordConfirmed: () => void; + setPasswordConfirmed?: () => void; onClick?: (password: string) => Promise; - loading?: boolean; + isLoading?: boolean; +} + +interface VerifyPasswordMessageEvent extends MessageEvent { + data: { + isPasswordCorrect: Yup.StringSchema< + string | undefined, + AnyObject, + string | undefined + >; + }; } export const PasswordProtectionPage = ({ setPasswordConfirmed, onClick, - loading = false + isLoading = false }: BackupSecretPhrasePasswordPageType) => { + const [isSubmitting, setIsSubmitting] = useState(false); + const { t } = useTranslation(); const passwordHash = useSelector(selectPasswordHash); const passwordSaltHash = useSelector(selectPasswordSaltHash); + const loginRetryCount = useSelector(selectLoginRetryCount); + + const attemptsLeft = + LOGIN_RETRY_ATTEMPTS_LIMIT - + loginRetryCount - + ERROR_DISPLAYED_BEFORE_ATTEMPT_IS_DECREMENTED; if (passwordHash == null || passwordSaltHash == null) { throw Error("Password doesn't exist"); @@ -43,26 +72,62 @@ export const PasswordProtectionPage = ({ const { register, handleSubmit, - formState: { errors, isDirty }, - getValues - } = useUnlockWalletForm(passwordHash, passwordSaltHash); - - const isSubmitButtonDisabled = calculateSubmitButtonDisabled({ - isDirty + formState: { errors }, + getValues, + setError + } = useForm({ + defaultValues: { + password: '' + } }); const onSubmit = () => { - if (onClick) { - const { password } = getValues(); - - onClick(password).then(() => { - setPasswordConfirmed(); - dispatchToMainStore(loginRetryCountReseted()); - }); - } else { - setPasswordConfirmed(); - dispatchToMainStore(loginRetryCountReseted()); - } + setIsSubmitting(true); + + const { password } = getValues(); + + const worker = new Worker( + new URL('@background/workers/verify-password-worker.ts', import.meta.url) + ); + + worker.postMessage({ + passwordHash, + passwordSaltHash, + password + }); + + worker.onmessage = (event: VerifyPasswordMessageEvent) => { + const { isPasswordCorrect } = event.data; + + if (!isPasswordCorrect) { + dispatchToMainStore(loginRetryCountIncremented()); + const errorMessage = getErrorMessageForIncorrectPassword(attemptsLeft); + + setError('password', { + message: t(errorMessage) + }); + setIsSubmitting(false); + } else { + if (onClick) { + onClick(password).then(() => { + if (setPasswordConfirmed) { + setPasswordConfirmed(); + } + dispatchToMainStore(loginRetryCountReseted()); + }); + } else { + if (setPasswordConfirmed) { + setPasswordConfirmed(); + } + dispatchToMainStore(loginRetryCountReseted()); + } + } + }; + + worker.onerror = error => { + console.error(error); + setIsSubmitting(false); + }; }; return ( @@ -84,8 +149,8 @@ export const PasswordProtectionPage = ({ )} renderFooter={() => ( - )} diff --git a/src/apps/popup/pages/remove-account/content.tsx b/src/apps/popup/pages/remove-account/content.tsx new file mode 100644 index 000000000..52ee0af2c --- /dev/null +++ b/src/apps/popup/pages/remove-account/content.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import { Trans, useTranslation } from 'react-i18next'; + +import { + ContentContainer, + IllustrationContainer, + ParagraphContainer, + SpacingSize +} from '@libs/layout'; +import { SvgIcon, Typography } from '@libs/ui/components'; + +export function RemoveAccountPageContent() { + const { t } = useTranslation(); + + return ( + + + + + + + Remove account? + + + + + + Are you sure you want to remove this account? The action can’t be + undone. + + + + + ); +} diff --git a/src/apps/popup/pages/remove-account/index.tsx b/src/apps/popup/pages/remove-account/index.tsx index 7bff66478..6bdd9a902 100644 --- a/src/apps/popup/pages/remove-account/index.tsx +++ b/src/apps/popup/pages/remove-account/index.tsx @@ -8,18 +8,19 @@ import { dispatchToMainStore } from '@background/redux/utils'; import { accountRemoved } from '@background/redux/vault/actions'; import { - ContentContainer, - FooterButtonsAbsoluteContainer, - IllustrationContainer, - ParagraphContainer, - SpacingSize + FooterButtonsContainer, + HeaderPopup, + HeaderSubmenuBarNavLink, + PopupLayout } from '@libs/layout'; -import { Button, SvgIcon, Typography } from '@libs/ui/components'; +import { Button } from '@libs/ui/components'; -export function RemoveAccountPageContent() { +import { RemoveAccountPageContent } from './content'; + +export const RemoveAccountPage = () => { + const { t } = useTranslation(); const navigate = useTypedNavigate(); const { accountName } = useParams(); - const { t } = useTranslation(); const handleRemoveAccount = useCallback(() => { if (!accountName) { @@ -37,35 +38,28 @@ export function RemoveAccountPageContent() { } return ( - - - ( + ( + + )} /> - - - - Remove account? - - - - - - Are you sure you want to remove this account? The action can’t be - undone. - - - - - - - - + )} + renderContent={() => } + renderFooter={() => ( + + + + + )} + /> ); -} +}; diff --git a/src/apps/popup/pages/rename-account/content.tsx b/src/apps/popup/pages/rename-account/content.tsx new file mode 100644 index 000000000..8dc119fcb --- /dev/null +++ b/src/apps/popup/pages/rename-account/content.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import { FieldErrors, UseFormRegister } from 'react-hook-form'; +import { Trans, useTranslation } from 'react-i18next'; + +import { + ContentContainer, + IllustrationContainer, + InputsContainer, + ParagraphContainer, + SpacingSize +} from '@libs/layout'; +import { Input, SvgIcon, Typography } from '@libs/ui/components'; +import { RenameAccountFormValues } from '@libs/ui/forms/rename-account'; + +interface RenameAccountPageContentProps { + register: UseFormRegister; + errors: FieldErrors; +} + +export function RenameAccountPageContent({ + register, + errors +}: RenameAccountPageContentProps) { + const { t } = useTranslation(); + + return ( + + + + + + + Rename account + + + + + + + ); +} diff --git a/src/apps/popup/pages/rename-account/index.tsx b/src/apps/popup/pages/rename-account/index.tsx index d21d73351..00cbd8b2f 100644 --- a/src/apps/popup/pages/rename-account/index.tsx +++ b/src/apps/popup/pages/rename-account/index.tsx @@ -10,20 +10,20 @@ import { accountRenamed } from '@background/redux/vault/actions'; import { selectVaultAccountsNames } from '@background/redux/vault/selectors'; import { - ContentContainer, - FooterButtonsAbsoluteContainer, - IllustrationContainer, - InputsContainer, - ParagraphContainer, - SpacingSize + FooterButtonsContainer, + HeaderPopup, + HeaderSubmenuBarNavLink, + PopupLayout } from '@libs/layout'; -import { Button, Input, SvgIcon, Typography } from '@libs/ui/components'; +import { Button } from '@libs/ui/components'; import { RenameAccountFormValues, useRenameAccount } from '@libs/ui/forms/rename-account'; -export function RenameAccountPageContent() { +import { RenameAccountPageContent } from './content'; + +export const RenameAccountPage = () => { const navigate = useTypedNavigate(); const { accountName } = useParams(); const { t } = useTranslation(); @@ -48,30 +48,24 @@ export function RenameAccountPageContent() { } return ( - - - ( + ( + + )} /> - - - - Rename account - - - - - - - + )} + renderContent={() => ( + + )} + renderFooter={() => ( + @@ -82,8 +76,8 @@ export function RenameAccountPageContent() { > Cancel - - - + + )} + /> ); -} +}; diff --git a/src/apps/popup/pages/stakes/amount-step.tsx b/src/apps/popup/pages/stakes/amount-step.tsx index afe66ba9f..217884dc0 100644 --- a/src/apps/popup/pages/stakes/amount-step.tsx +++ b/src/apps/popup/pages/stakes/amount-step.tsx @@ -36,7 +36,7 @@ const StakeMaxButton = styled(AlignedFlexRow)<{ disabled?: boolean }>` interface AmountStepProps { amountForm: UseFormReturn; - stakesType: AuctionManagerEntryPoint; + stakeType: AuctionManagerEntryPoint; stakeAmountMotes: string; amountStepText: string; amountStepMaxAmountValue: string | null; @@ -44,7 +44,7 @@ interface AmountStepProps { export const AmountStep = ({ amountForm, - stakesType, + stakeType, stakeAmountMotes, amountStepText, amountStepMaxAmountValue @@ -58,7 +58,7 @@ export const AmountStep = ({ const csprBalance = useSelector(selectAccountBalance); useEffect(() => { - switch (stakesType) { + switch (stakeType) { case AuctionManagerEntryPoint.delegate: { const maxAmountMotes: string = csprBalance.liquidMotes == null @@ -86,7 +86,7 @@ export const AmountStep = ({ setMaxAmountMotes(stakeAmountMotes); } } - }, [csprBalance.liquidMotes, stakeAmountMotes, stakesType]); + }, [csprBalance.liquidMotes, stakeAmountMotes, stakeType]); const { register, @@ -112,7 +112,7 @@ export const AmountStep = ({ { const { t } = useTranslation(); const currencyRate = useSelector(selectAccountCurrencyRate); - const transferFeeMotes = getAuctionManagerDeployCost(stakesType); + const transferFeeMotes = getAuctionManagerDeployCost(stakeType); const transferCostInCSPR = formatNumber(motesToCSPR(transferFeeMotes), { precision: { max: 5 } @@ -102,7 +98,7 @@ export const ConfirmStep = ({ } delegatorsNumber={validator?.delegators_number} validatorLabel={ - stakesType === AuctionManagerEntryPoint.redelegate + stakeType === AuctionManagerEntryPoint.redelegate ? t('From validator') : t('To validator') } diff --git a/src/apps/popup/pages/stakes/content.tsx b/src/apps/popup/pages/stakes/content.tsx index 5492eb9ef..e69de29bb 100644 --- a/src/apps/popup/pages/stakes/content.tsx +++ b/src/apps/popup/pages/stakes/content.tsx @@ -1,164 +0,0 @@ -import React from 'react'; -import { UseFormReturn } from 'react-hook-form'; -import { Trans, useTranslation } from 'react-i18next'; - -import { AuctionManagerEntryPoint, StakeSteps } from '@src/constants'; - -import { AmountStep } from '@popup/pages/stakes/amount-step'; -import { ConfirmStep } from '@popup/pages/stakes/confirm-step'; -import { RedelegateValidatorDropdownInput } from '@popup/pages/stakes/redelegate-validator-dropdown-input'; -import { Step } from '@popup/pages/stakes/step'; -import { useStakeActionTexts } from '@popup/pages/stakes/utils'; -import { ValidatorDropdownInput } from '@popup/pages/stakes/validator-dropdown-input'; - -import { - AlignedFlexRow, - ParagraphContainer, - SpacingSize, - VerticalSpaceContainer -} from '@libs/layout'; -import { ILedgerEvent } from '@libs/services/ledger'; -import { ValidatorResultWithId } from '@libs/services/validators-service/types'; -import { - LedgerEventView, - TransferSuccessScreen, - Typography -} from '@libs/ui/components'; -import { - StakeAmountFormValues, - StakeNewValidatorFormValues, - StakeValidatorFormValues -} from '@libs/ui/forms/stakes-form'; - -interface DelegateStakePageContentProps { - stakeStep: StakeSteps; - validatorForm: UseFormReturn; - amountForm: UseFormReturn; - newValidatorForm: UseFormReturn; - inputAmountCSPR: string; - validator: ValidatorResultWithId | null; - setValidator: React.Dispatch< - React.SetStateAction - >; - newValidator: ValidatorResultWithId | null; - setNewValidator: React.Dispatch< - React.SetStateAction - >; - stakesType: AuctionManagerEntryPoint; - stakeAmountMotes: string; - setStakeAmount: React.Dispatch>; - validatorList: ValidatorResultWithId[] | null; - undelegateValidatorList: ValidatorResultWithId[] | null; - loading: boolean; - LedgerEventStatus: ILedgerEvent; -} - -export const StakesPageContent = ({ - stakeStep, - validatorForm, - amountForm, - newValidatorForm, - inputAmountCSPR, - validator, - setValidator, - newValidator, - setNewValidator, - stakesType, - stakeAmountMotes, - setStakeAmount, - validatorList, - undelegateValidatorList, - loading, - LedgerEventStatus -}: DelegateStakePageContentProps) => { - const { t } = useTranslation(); - - const { - validatorStepHeaderText, - newValidatorStepHeaderText, - amountStepHeaderText, - confirmStepHeaderText, - successStepHeaderText, - confirmStepText, - amountStepText, - amountStepMaxAmountValue - } = useStakeActionTexts(stakesType, stakeAmountMotes); - - const getContent = { - [StakeSteps.Validator]: ( - - - - ), - [StakeSteps.Amount]: ( - - - - ), - [StakeSteps.NewValidator]: ( - - - - - Amount: - - {`${inputAmountCSPR} CSPR`} - - - - - ), - [StakeSteps.Confirm]: ( - - - - ), - [StakeSteps.ConfirmWithLedger]: ( - - ), - [StakeSteps.Success]: ( - - {stakesType === AuctionManagerEntryPoint.redelegate ? ( - - - - I usually takes around{' '} - 14 to 16 hours for - this operation to complete. - - - - ) : null} - - ) - }; - - return getContent[stakeStep]; -}; diff --git a/src/apps/popup/pages/stakes/index.tsx b/src/apps/popup/pages/stakes/index.tsx index 698153f33..42c97f58b 100644 --- a/src/apps/popup/pages/stakes/index.tsx +++ b/src/apps/popup/pages/stakes/index.tsx @@ -1,7 +1,8 @@ import { DeployUtil } from 'casper-js-sdk'; -import React, { useEffect, useState } from 'react'; +import React, { useState } from 'react'; import { Trans, useTranslation } from 'react-i18next'; import { useSelector } from 'react-redux'; +import styled from 'styled-components'; import { AuctionManagerEntryPoint, @@ -10,10 +11,18 @@ import { } from '@src/constants'; import { fetchAndDispatchExtendedDeployInfo } from '@src/utils'; -import { StakesPageContent } from '@popup/pages/stakes/content'; +import { AmountStep } from '@popup/pages/stakes/amount-step'; +import { ConfirmStep } from '@popup/pages/stakes/confirm-step'; import { NoDelegations } from '@popup/pages/stakes/no-delegations'; -import { useConfirmationButtonText } from '@popup/pages/stakes/utils'; -import { RouterPath, useTypedLocation, useTypedNavigate } from '@popup/router'; +import { RedelegateValidatorDropdownInput } from '@popup/pages/stakes/redelegate-validator-dropdown-input'; +import { Step } from '@popup/pages/stakes/step'; +import { + useConfirmationButtonText, + useStakeActionTexts, + useStakeType +} from '@popup/pages/stakes/utils'; +import { ValidatorDropdownInput } from '@popup/pages/stakes/validator-dropdown-input'; +import { RouterPath, useTypedNavigate } from '@popup/router'; import { selectAccountBalance } from '@background/redux/account-info/selectors'; import { ledgerDeployChanged } from '@background/redux/ledger/actions'; @@ -29,17 +38,21 @@ import { } from '@background/redux/vault/selectors'; import { useLedger } from '@hooks/use-ledger'; +import { useSubmitButton } from '@hooks/use-submit-button'; import { createAsymmetricKey } from '@libs/crypto/create-asymmetric-key'; import { AlignedFlexRow, + CenteredFlexRow, ErrorPath, FooterButtonsContainer, HeaderPopup, HeaderSubmenuBarNavLink, + ParagraphContainer, PopupLayout, SpaceBetweenFlexRow, SpacingSize, + VerticalSpaceContainer, createErrorLocationState } from '@libs/layout'; import { @@ -47,15 +60,13 @@ import { sendSignDeploy, signDeploy } from '@libs/services/deployer-service'; -import { - dispatchFetchAuctionValidatorsRequest, - dispatchFetchValidatorsDetailsDataRequest -} from '@libs/services/validators-service'; import { ValidatorResultWithId } from '@libs/services/validators-service/types'; import { Button, HomePageTabsId, + LedgerEventView, SvgIcon, + TransferSuccessScreen, Typography, renderLedgerFooter } from '@libs/ui/components'; @@ -63,134 +74,69 @@ import { calculateSubmitButtonDisabled } from '@libs/ui/forms/get-submit-button- import { useStakesForm } from '@libs/ui/forms/stakes-form'; import { CSPRtoMotes, formatNumber, motesToCSPR } from '@libs/ui/utils'; +const ScrollContainer = styled(VerticalSpaceContainer)<{ + isHidden: boolean; +}>` + opacity: ${({ isHidden }) => (isHidden ? '0' : '1')}; + height: ${({ isHidden }) => (isHidden ? '0' : '24px')}; + visibility: ${({ isHidden }) => (isHidden ? 'hidden' : 'visible')}; + transition: + opacity 0.2s ease-in-out, + height 0.5s ease-in-out; +`; + +const ConfirmButtonContainer = styled(FooterButtonsContainer)<{ + isHidden: boolean; +}>` + gap: ${({ isHidden }) => (isHidden ? '0' : '16px')}; + transition: gap 0.5s ease-in-out; +`; + export const StakesPage = () => { const [stakeStep, setStakeStep] = useState(StakeSteps.Validator); const [validatorPublicKey, setValidatorPublicKey] = useState(''); const [newValidatorPublicKey, setNewValidatorPublicKey] = useState(''); const [inputAmountCSPR, setInputAmountCSPR] = useState(''); - const [isSubmitButtonDisable, setIsSubmitButtonDisable] = useState(true); const [validator, setValidator] = useState( null ); const [newValidator, setNewValidator] = useState(null); - const [stakesType, setStakesType] = useState( - AuctionManagerEntryPoint.delegate - ); const [stakeAmountMotes, setStakeAmountMotes] = useState(''); - const [validatorList, setValidatorList] = useState< - ValidatorResultWithId[] | null - >(null); - const [undelegateValidatorList, setUndelegateValidatorList] = useState< - ValidatorResultWithId[] | null - >(null); - const [loading, setLoading] = useState(true); const activeAccount = useSelector(selectVaultActiveAccount); const isActiveAccountFromLedger = useSelector( selectIsActiveAccountFromLedger ); - const { - networkName, - nodeUrl, - auctionManagerContractHash, - casperClarityApiUrl - } = useSelector(selectApiConfigBasedOnActiveNetwork); + const { networkName, nodeUrl, auctionManagerContractHash } = useSelector( + selectApiConfigBasedOnActiveNetwork + ); const csprBalance = useSelector(selectAccountBalance); const ratedInStore = useSelector(selectRatedInStore); const askForReviewAfter = useSelector(selectAskForReviewAfter); const { t } = useTranslation(); const navigate = useTypedNavigate(); - const { pathname } = useTypedLocation(); - - useEffect(() => { - // checking pathname to know what type of stake it is - if (pathname.split('/')[1] === AuctionManagerEntryPoint.delegate) { - setStakesType(AuctionManagerEntryPoint.delegate); - - dispatchFetchAuctionValidatorsRequest() - .then(({ payload }) => { - if ('data' in payload) { - const { data } = payload; - - const validatorListWithId = data.map(validator => ({ - ...validator, - id: validator.public_key - })); - - setValidatorList(validatorListWithId); - } - }) - .finally(() => { - setLoading(false); - }); - } else if (pathname.split('/')[1] === AuctionManagerEntryPoint.undelegate) { - setStakesType(AuctionManagerEntryPoint.undelegate); - - if (activeAccount) { - dispatchFetchValidatorsDetailsDataRequest(activeAccount.publicKey) - .then(({ payload }) => { - if ('data' in payload) { - const { data } = payload; - - const validatorListWithId = data.map(delegator => ({ - ...delegator.validator, - id: delegator.validator_public_key, - user_stake: delegator.stake - })); - - setUndelegateValidatorList(validatorListWithId); - } - }) - .finally(() => { - setLoading(false); - }); - } - } else if (pathname.split('/')[1] === AuctionManagerEntryPoint.redelegate) { - setStakesType(AuctionManagerEntryPoint.redelegate); - - if (activeAccount) { - Promise.all([ - dispatchFetchAuctionValidatorsRequest(), - dispatchFetchValidatorsDetailsDataRequest(activeAccount.publicKey) - ]) - .then(([allValidatorsResp, undelegateValidatorResp]) => { - if ('data' in allValidatorsResp.payload) { - const { data } = allValidatorsResp.payload; - - const validatorListWithId = data.map(validator => ({ - ...validator, - id: validator.public_key - })); - - setValidatorList(validatorListWithId); - } - if ('data' in undelegateValidatorResp.payload) { - const { data } = undelegateValidatorResp.payload; - const validatorListWithId = data.map(delegator => ({ - ...delegator.validator, - id: delegator.validator_public_key, - user_stake: delegator.stake - })); + const { stakeType, validatorList, undelegateValidatorList, loading } = + useStakeType(); - setUndelegateValidatorList(validatorListWithId); - } - }) - .finally(() => { - setLoading(false); - }); - } - } - }, [activeAccount, pathname, casperClarityApiUrl]); + const hasDelegationToSelectedValidator = undelegateValidatorList?.some( + accountDelegation => accountDelegation.public_key === validator?.public_key + ); + const hasDelegationToSelectedNewValidator = undelegateValidatorList?.some( + accountDelegation => + accountDelegation.public_key === newValidator?.public_key + ); const { amountForm, validatorForm, newValidatorForm } = useStakesForm( csprBalance.liquidMotes, - stakesType, + stakeType, stakeAmountMotes, validator?.delegators_number, - newValidator?.delegators_number + newValidator?.delegators_number, + hasDelegationToSelectedValidator, + hasDelegationToSelectedNewValidator ); const { formState: amountFormState, getValues: getValuesAmountForm } = amountForm; @@ -201,46 +147,15 @@ export const StakesPage = () => { getValues: getValuesNewValidatorForm } = newValidatorForm; - // event listener for enable/disable submit button - useEffect(() => { - if (stakeStep !== StakeSteps.Confirm) return; - - const layoutContentContainer = document.querySelector('#ms-container'); - - // if the content is not scrollable, we can enable the submit button - if ( - layoutContentContainer && - layoutContentContainer.clientHeight === - layoutContentContainer.scrollHeight && - isSubmitButtonDisable - ) { - setIsSubmitButtonDisable(false); - } - - const handleScroll = () => { - if (layoutContentContainer && isSubmitButtonDisable) { - const bottom = - Math.ceil( - layoutContentContainer.clientHeight + - layoutContentContainer.scrollTop - ) >= layoutContentContainer.scrollHeight; - - if (bottom) { - setIsSubmitButtonDisable(false); - } - } - }; - - // add event listener to the scrollable container - layoutContentContainer?.addEventListener('scroll', handleScroll); - - // remove event listener on cleanup - return () => { - layoutContentContainer?.removeEventListener('scroll', handleScroll); - }; - }, [isSubmitButtonDisable, stakeStep]); + const { + isSubmitButtonDisable, + setIsSubmitButtonDisable, + isAdditionalTextVisible + } = useSubmitButton(stakeStep === StakeSteps.Confirm); const submitStake = async () => { + setIsSubmitButtonDisable(true); + if (activeAccount) { const motesAmount = CSPRtoMotes(inputAmountCSPR); @@ -250,7 +165,7 @@ export const StakesPage = () => { ); const deploy = await makeAuctionManagerDeploy( - stakesType, + stakeType, activeAccount.publicKey, validatorPublicKey, newValidatorPublicKey || null, @@ -313,7 +228,7 @@ export const StakesPage = () => { const motesAmount = CSPRtoMotes(inputAmountCSPR); const deploy = await makeAuctionManagerDeploy( - stakesType, + stakeType, activeAccount.publicKey, validatorPublicKey, newValidatorPublicKey || null, @@ -334,70 +249,262 @@ export const StakesPage = () => { beforeLedgerActionCb }); - const getButtonProps = () => { - const isValidatorFormButtonDisabled = calculateSubmitButtonDisabled({ - isValid: validatorFormState.isValid - }); - const isAmountFormButtonDisabled = calculateSubmitButtonDisabled({ - isValid: amountFormState.isValid - }); - const isNewValidatorFormButtonDisabled = calculateSubmitButtonDisabled({ - isValid: newValidatorFormState.isValid - }); - - switch (stakeStep) { - case StakeSteps.Validator: { - return { - disabled: isValidatorFormButtonDisabled, - onClick: () => { + const isValidatorFormButtonDisabled = calculateSubmitButtonDisabled({ + isValid: validatorFormState.isValid + }); + const isAmountFormButtonDisabled = calculateSubmitButtonDisabled({ + isValid: amountFormState.isValid + }); + const isNewValidatorFormButtonDisabled = calculateSubmitButtonDisabled({ + isValid: newValidatorFormState.isValid + }); + + const { + validatorStepHeaderText, + newValidatorStepHeaderText, + amountStepHeaderText, + confirmStepHeaderText, + successStepHeaderText, + confirmStepText, + amountStepText, + amountStepMaxAmountValue + } = useStakeActionTexts(stakeType, stakeAmountMotes); + + const confirmButtonText = useConfirmationButtonText(stakeType); + + const content = { + [StakeSteps.Validator]: ( + + + + ), + [StakeSteps.Amount]: ( + + + + ), + [StakeSteps.NewValidator]: ( + + + + + Amount: + + {`${inputAmountCSPR} CSPR`} + + + + + ), + [StakeSteps.Confirm]: ( + + + + ), + [StakeSteps.ConfirmWithLedger]: ( + + ), + [StakeSteps.Success]: ( + + {stakeType === AuctionManagerEntryPoint.redelegate ? ( + + + + I usually takes around{' '} + 14 to 16 hours for + this operation to complete. + + + + ) : null} + + ) + }; + + const headerButtons = { + [StakeSteps.Validator]: ( + + ), + [StakeSteps.Amount]: ( + setStakeStep(StakeSteps.Validator)} + /> + ), + [StakeSteps.NewValidator]: ( + setStakeStep(StakeSteps.Amount)} + /> + ), + [StakeSteps.Confirm]: ( + + stakeType === AuctionManagerEntryPoint.redelegate + ? setStakeStep(StakeSteps.NewValidator) + : setStakeStep(StakeSteps.Amount) + } + /> + ), + [StakeSteps.ConfirmWithLedger]: ( + setStakeStep(StakeSteps.Confirm)} + /> + ) + }; + + const ledgerFooterButton = renderLedgerFooter({ + onConnect: makeSubmitLedgerAction, + event: ledgerEventStatusToRender, + onErrorCtaPressed: () => setStakeStep(StakeSteps.Confirm) + }); + + const footerButtons = { + [StakeSteps.Validator]: ( + + + + ), + [StakeSteps.Amount]: ( + + + + Transaction fee + + + {formatNumber(motesToCSPR(STAKE_COST_MOTES), { + precision: { max: 5 } + })}{' '} + CSPR + + + + + ), + [StakeSteps.NewValidator]: ( + + + + ), + [StakeSteps.Confirm]: ( + + + + + Scroll down to check all details + + + + + + ), + [StakeSteps.ConfirmWithLedger]: ledgerFooterButton ? ( + ledgerFooterButton() + ) : ( + <> + ), + [StakeSteps.Success]: ( + + + + ) }; - const confirmButtonText = useConfirmationButtonText(stakesType); - if ( - (stakesType === AuctionManagerEntryPoint.undelegate || - stakesType === AuctionManagerEntryPoint.redelegate) && + (stakeType === AuctionManagerEntryPoint.undelegate || + stakeType === AuctionManagerEntryPoint.redelegate) && (csprBalance.delegatedMotes == null || csprBalance.delegatedMotes === '0') ) { return ( @@ -494,50 +559,6 @@ export const StakesPage = () => { ); } - const renderFooter = () => { - if (stakeStep === StakeSteps.ConfirmWithLedger) { - return renderLedgerFooter({ - onConnect: makeSubmitLedgerAction, - event: ledgerEventStatusToRender, - onErrorCtaPressed: () => setStakeStep(StakeSteps.Confirm) - }); - } - - return () => ( - - {stakeStep === StakeSteps.Amount ? ( - - - Transaction fee - - - {formatNumber(motesToCSPR(STAKE_COST_MOTES), { - precision: { max: 5 } - })}{' '} - CSPR - - - ) : null} - - - ); - }; - return ( ( @@ -545,30 +566,15 @@ export const StakesPage = () => { withNetworkSwitcher withMenu withConnectionStatus - renderSubmenuBarItems={getBackButton[stakeStep]} - /> - )} - renderContent={() => ( - headerButtons[stakeStep] + } /> )} - renderFooter={renderFooter()} + renderContent={() => content[stakeStep]} + renderFooter={() => footerButtons[stakeStep]} /> ); }; diff --git a/src/apps/popup/pages/stakes/redelegate-validator-dropdown-input.tsx b/src/apps/popup/pages/stakes/redelegate-validator-dropdown-input.tsx index 9750d797a..3cba5a4c4 100644 --- a/src/apps/popup/pages/stakes/redelegate-validator-dropdown-input.tsx +++ b/src/apps/popup/pages/stakes/redelegate-validator-dropdown-input.tsx @@ -107,7 +107,7 @@ export const RedelegateValidatorDropdownInput = ({ validator?.account_info?.info?.owner?.branding?.logo?.png_256 || validator?.account_info?.info?.owner?.branding?.logo?.png_1024 } - totalStake={validator.user_stake} + totalStake={validator.total_stake} delegatorsNumber={validator?.delegators_number} validatorLabel={t('To validator')} error={errors?.newValidatorPublicKey} @@ -179,7 +179,7 @@ export const RedelegateValidatorDropdownInput = ({ fee={validator.fee} name={validator?.account_info?.info?.owner?.name} logo={logo} - totalStake={validator.user_stake} + totalStake={validator.total_stake} delegatorsNumber={validator?.delegators_number} handleClick={() => { setValue('newValidatorPublicKey', validator.public_key); diff --git a/src/apps/popup/pages/stakes/utils.ts b/src/apps/popup/pages/stakes/utils.ts index 3fec838e9..c7662d42b 100644 --- a/src/apps/popup/pages/stakes/utils.ts +++ b/src/apps/popup/pages/stakes/utils.ts @@ -1,9 +1,19 @@ import { useCallback, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; +import { useSelector } from 'react-redux'; import { AuctionManagerEntryPoint } from '@src/constants'; -import { ValidatorResultWithId } from '@libs/services/validators-service'; +import { useTypedLocation } from '@popup/router'; + +import { selectApiConfigBasedOnActiveNetwork } from '@background/redux/settings/selectors'; +import { selectVaultActiveAccount } from '@background/redux/vault/selectors'; + +import { + ValidatorResultWithId, + dispatchFetchAuctionValidatorsRequest, + dispatchFetchValidatorsDetailsDataRequest +} from '@libs/services/validators-service'; import { formatNumber, motesToCSPR } from '@libs/ui/utils'; type StakeTexts = { @@ -81,11 +91,11 @@ export const useFilteredValidators = ( }; export const useStakeActionTexts = ( - stakesType: AuctionManagerEntryPoint, + stakeType: AuctionManagerEntryPoint, stakeAmountMotes?: string ) => { const [state, setState] = useState({ - ...stakeActionsTextMap[stakesType], + ...stakeActionsTextMap[stakeType], amountStepMaxAmountValue: null }); @@ -95,19 +105,19 @@ export const useStakeActionTexts = ( formatNumber(motesToCSPR(stakeAmountMotes), { precision: { max: 4 } }); setState({ - ...stakeActionsTextMap[stakesType], + ...stakeActionsTextMap[stakeType], amountStepMaxAmountValue: - stakesType !== AuctionManagerEntryPoint.delegate + stakeType !== AuctionManagerEntryPoint.delegate ? `${formattedAmountCSPR} CSPR` : null }); - }, [stakeAmountMotes, stakesType]); + }, [stakeAmountMotes, stakeType]); return state; }; export const useConfirmationButtonText = ( - stakesType: AuctionManagerEntryPoint + stakeType: AuctionManagerEntryPoint ) => { const { t } = useTranslation(); @@ -118,5 +128,98 @@ export const useConfirmationButtonText = ( default: t('Confirm') }; - return buttonTexts[stakesType] || buttonTexts.default; + return buttonTexts[stakeType] || buttonTexts.default; +}; + +export const useStakeType = () => { + const [stakeType, setStakeType] = useState( + AuctionManagerEntryPoint.delegate + ); + const [validatorList, setValidatorList] = useState< + ValidatorResultWithId[] | null + >(null); + const [undelegateValidatorList, setUndelegateValidatorList] = useState< + ValidatorResultWithId[] | null + >(null); + const [loading, setLoading] = useState(true); + + const { pathname } = useTypedLocation(); + + const activeAccount = useSelector(selectVaultActiveAccount); + const { casperClarityApiUrl } = useSelector( + selectApiConfigBasedOnActiveNetwork + ); + + useEffect(() => { + const name = pathname.split('/')[1]; + // checking pathname to know what type of stake it is + if ( + name === AuctionManagerEntryPoint.delegate || + name === AuctionManagerEntryPoint.redelegate + ) { + setStakeType(name); + + if (activeAccount) { + Promise.all([ + dispatchFetchAuctionValidatorsRequest(), + dispatchFetchValidatorsDetailsDataRequest(activeAccount.publicKey) + ]) + .then(([allValidatorsResp, undelegateValidatorResp]) => { + if ('data' in allValidatorsResp.payload) { + const { data } = allValidatorsResp.payload; + + const validatorListWithId = data.map(validator => ({ + ...validator, + id: validator.public_key + })); + + setValidatorList(validatorListWithId); + } + if ('data' in undelegateValidatorResp.payload) { + const { data } = undelegateValidatorResp.payload; + + const validatorListWithId = data.map(delegator => ({ + ...delegator.validator, + id: delegator.validator_public_key, + user_stake: delegator.stake + })); + + setUndelegateValidatorList(validatorListWithId); + } + }) + .finally(() => { + setLoading(false); + }); + } + } else if (name === AuctionManagerEntryPoint.undelegate) { + setStakeType(name); + + if (activeAccount) { + dispatchFetchValidatorsDetailsDataRequest(activeAccount.publicKey) + .then(({ payload }) => { + if ('data' in payload) { + const { data } = payload; + + const validatorListWithId = data.map(delegator => ({ + ...delegator.validator, + id: delegator.validator_public_key, + user_stake: delegator.stake + })); + + setUndelegateValidatorList(validatorListWithId); + } + }) + .finally(() => { + setLoading(false); + }); + } + } + }, [activeAccount, pathname, casperClarityApiUrl]); + + return { + stakeType, + validatorList, + undelegateValidatorList, + loading + }; }; diff --git a/src/apps/popup/pages/stakes/validator-dropdown-input.tsx b/src/apps/popup/pages/stakes/validator-dropdown-input.tsx index d2ab05703..8b28643b0 100644 --- a/src/apps/popup/pages/stakes/validator-dropdown-input.tsx +++ b/src/apps/popup/pages/stakes/validator-dropdown-input.tsx @@ -33,8 +33,7 @@ interface ValidatorDropdownInputProps { React.SetStateAction >; setStakeAmount: React.Dispatch>; - stakesType: AuctionManagerEntryPoint; - undelegateValidatorList: ValidatorResultWithId[] | null; + stakeType: AuctionManagerEntryPoint; loading: boolean; } @@ -44,8 +43,7 @@ export const ValidatorDropdownInput = ({ validator, setValidator, setStakeAmount, - stakesType, - undelegateValidatorList, + stakeType, loading }: ValidatorDropdownInputProps) => { const [isOpenValidatorPublicKeysList, setIsOpenValidatorPublicKeysList] = @@ -103,11 +101,11 @@ export const ValidatorDropdownInput = ({ const filteredValidatorsList = useFilteredValidators( inputValue, - undelegateValidatorList || validatorList + validatorList ); useEffect(() => { - switch (stakesType) { + switch (stakeType) { case AuctionManagerEntryPoint.delegate: { setLabel('To validator'); break; @@ -118,7 +116,7 @@ export const ValidatorDropdownInput = ({ break; } } - }, [stakesType]); + }, [stakeType]); return showValidatorPlate && validator ? ( @@ -133,7 +131,7 @@ export const ValidatorDropdownInput = ({ } // TODO: remove user_stake after we merge recipient and amount steps for undelegation totalStake={ - stakesType === AuctionManagerEntryPoint.delegate + stakeType === AuctionManagerEntryPoint.delegate ? validator.total_stake : validator.user_stake } @@ -211,7 +209,7 @@ export const ValidatorDropdownInput = ({ logo={logo} // TODO: remove user_stake after we merge recipient and amount steps for undelegation totalStake={ - stakesType === AuctionManagerEntryPoint.delegate + stakeType === AuctionManagerEntryPoint.delegate ? validator.total_stake : validator.user_stake } diff --git a/src/apps/popup/pages/token-details/token.tsx b/src/apps/popup/pages/token-details/token.tsx index 081d80b7c..4d02143c4 100644 --- a/src/apps/popup/pages/token-details/token.tsx +++ b/src/apps/popup/pages/token-details/token.tsx @@ -133,18 +133,7 @@ export const Token = ({ erc20Tokens }: TokenProps) => { - navigate( - tokenData?.id - ? RouterPath.Transfer.replace( - ':tokenContractPackageHash', - tokenData.id - ).replace( - ':tokenContractHash', - tokenData.contractHash || 'null' - ) - : RouterPath.TransferNoParams, - { state: { tokenData } } - ) + navigate(RouterPath.Transfer, { state: { tokenData } }) } > + + ), + [TransferNFTSteps.Recipient]: ( + + + + ), + [TransferNFTSteps.Confirm]: ( + + + + ), + [TransferNFTSteps.ConfirmWithLedger]: ledgerFooterButton ? ( + ledgerFooterButton() + ) : ( + <> + ), + [TransferNFTSteps.Success]: ( - {showSuccessScreen ? ( - <> - - - {!isRecipientPublicKeyInContact && ( - - )} - - ) : ( - + + {!isRecipientPublicKeyInContact && ( + )} - ); + ) }; return ( @@ -357,34 +455,14 @@ export const TransferNftPage = () => { withMenu withConnectionStatus renderSubmenuBarItems={ - showSuccessScreen + transferNFTStep === TransferNFTSteps.Success ? undefined - : showLedgerConfirm - ? () => ( - setShowLedgerConfirm(false)} - /> - ) - : () => + : () => headerButtons[transferNFTStep] } /> )} - renderContent={() => - showSuccessScreen ? ( - - ) : showLedgerConfirm ? ( - - ) : ( - - ) - } - renderFooter={renderFooter()} + renderContent={() => content[transferNFTStep]} + renderFooter={() => footerButtons[transferNFTStep]} /> ); }; diff --git a/src/apps/popup/pages/transfer-nft/recipient-step.tsx b/src/apps/popup/pages/transfer-nft/recipient-step.tsx new file mode 100644 index 000000000..432758855 --- /dev/null +++ b/src/apps/popup/pages/transfer-nft/recipient-step.tsx @@ -0,0 +1,42 @@ +import React from 'react'; +import { UseFormReturn } from 'react-hook-form'; +import { Trans, useTranslation } from 'react-i18next'; + +import { + ContentContainer, + ParagraphContainer, + SpacingSize +} from '@libs/layout'; +import { Typography } from '@libs/ui/components'; +import { RecipientTabs } from '@libs/ui/components/recipient-tabs/recipient-tabs'; +import { TransferNftRecipientFormValues } from '@libs/ui/forms/transfer-nft'; + +interface RecipientStepProps { + recipientForm: UseFormReturn; + setRecipientName: React.Dispatch>; + recipientName: string; +} + +export const RecipientStep = ({ + recipientForm, + setRecipientName, + recipientName +}: RecipientStepProps) => { + const { t } = useTranslation(); + + return ( + + + + Select recipient + + + + + + ); +}; diff --git a/src/apps/popup/pages/transfer-nft/review-step.tsx b/src/apps/popup/pages/transfer-nft/review-step.tsx new file mode 100644 index 000000000..aa22224c0 --- /dev/null +++ b/src/apps/popup/pages/transfer-nft/review-step.tsx @@ -0,0 +1,96 @@ +import React from 'react'; +import { UseFormReturn, useWatch } from 'react-hook-form'; +import { Trans, useTranslation } from 'react-i18next'; +import { useSelector } from 'react-redux'; + +import { selectAccountCurrencyRate } from '@background/redux/account-info/selectors'; + +import { + ContentContainer, + ParagraphContainer, + SpacingSize, + VerticalSpaceContainer +} from '@libs/layout'; +import { NFTTokenResult } from '@libs/services/nft-service'; +import { Input, Typography } from '@libs/ui/components'; +import { TransferNftAmountFormValues } from '@libs/ui/forms/transfer-nft'; +import { formatFiatAmount, handleNumericInput } from '@libs/ui/utils'; + +import { NFTPlate } from './components/nft-plate'; +import { NFTData } from './utils'; + +interface ReviewStepProps { + nftToken: NFTTokenResult | undefined; + haveReverseOwnerLookUp: boolean; + amountForm: UseFormReturn; + nftData?: NFTData; +} + +export const ReviewStep = ({ + nftToken, + haveReverseOwnerLookUp, + amountForm, + nftData +}: ReviewStepProps) => { + const { t } = useTranslation(); + + const currencyRate = useSelector(selectAccountCurrencyRate); + + const { + register, + formState: { errors }, + control + } = amountForm; + + const paymentAmount = useWatch({ + control, + name: 'paymentAmount' + }); + + const paymentFiatAmount = formatFiatAmount( + paymentAmount || '0', + currencyRate + ); + + return ( + + + + Review details + + + + + + + + {haveReverseOwnerLookUp && ( + + + + Sorry, but we don’t support the reverse look-up modality + + + + )} + + + + + + ); +}; diff --git a/src/apps/popup/pages/transfer-nft/utils.ts b/src/apps/popup/pages/transfer-nft/utils.ts index 7f62a4845..cc094c015 100644 --- a/src/apps/popup/pages/transfer-nft/utils.ts +++ b/src/apps/popup/pages/transfer-nft/utils.ts @@ -85,3 +85,16 @@ export const getRuntimeArgs = ( throw new Error('Unknown token standard.'); } }; + +export enum TransferNFTSteps { + Review = 'review', + Recipient = 'recipient', + Confirm = 'confirm', + ConfirmWithLedger = 'confirm with ledger', + Success = 'success' +} + +export interface NFTData { + contentType: string; + url?: string; +} diff --git a/src/apps/popup/pages/transfer/amount-step.tsx b/src/apps/popup/pages/transfer/amount-step.tsx index 9ed16698d..71c4333b1 100644 --- a/src/apps/popup/pages/transfer/amount-step.tsx +++ b/src/apps/popup/pages/transfer/amount-step.tsx @@ -1,6 +1,6 @@ import Big from 'big.js'; import React, { useEffect, useState } from 'react'; -import { UseFormReturn, useWatch } from 'react-hook-form'; +import { useWatch } from 'react-hook-form'; import { Trans, useTranslation } from 'react-i18next'; import { useSelector } from 'react-redux'; @@ -10,6 +10,10 @@ import { selectAccountBalance, selectAccountCurrencyRate } from '@background/redux/account-info/selectors'; +import { selectApiConfigBasedOnActiveNetwork } from '@background/redux/settings/selectors'; +import { selectVaultActiveAccount } from '@background/redux/vault/selectors'; + +import { TokenType } from '@hooks/use-casper-token'; import { ContentContainer, @@ -17,22 +21,36 @@ import { SpacingSize, VerticalSpaceContainer } from '@libs/layout'; -import { Checkbox, Error, Input, Typography } from '@libs/ui/components'; -import { TransferAmountFormValues } from '@libs/ui/forms/transfer'; +import { Error, Input, Typography } from '@libs/ui/components'; +import { TransactionFeePlate } from '@libs/ui/components/transaction-fee-plate/transaction-fee-plate'; +import { calculateSubmitButtonDisabled } from '@libs/ui/forms/get-submit-button-state-from-validation'; +import { useTransferAmountForm } from '@libs/ui/forms/transfer'; import { + divideErc20Balance, formatFiatAmount, handleNumericInput, motesToCSPR } from '@libs/ui/utils'; interface AmountStepProps { - amountForm: UseFormReturn; - symbol: string | null; - isCSPR: boolean; + isErc20Transfer: boolean; + paymentAmount: string; + selectedToken: TokenType | null | undefined; + setAmount: (value: React.SetStateAction) => void; + setPaymentAmount: (value: React.SetStateAction) => void; + setTransferIdMemo: (value: React.SetStateAction) => void; + setIsAmountFormButtonDisabled: React.Dispatch>; } -export const AmountStep = ({ amountForm, symbol, isCSPR }: AmountStepProps) => { - const [isChecked, setIsChecked] = useState(false); +export const AmountStep = ({ + isErc20Transfer, + paymentAmount, + selectedToken, + setAmount, + setPaymentAmount, + setTransferIdMemo, + setIsAmountFormButtonDisabled +}: AmountStepProps) => { const [disabled, setDisabled] = useState(false); const [maxAmountMotes, setMaxAmountMotes] = useState('0'); @@ -40,6 +58,24 @@ export const AmountStep = ({ amountForm, symbol, isCSPR }: AmountStepProps) => { const currencyRate = useSelector(selectAccountCurrencyRate); const csprBalance = useSelector(selectAccountBalance); + const activeAccount = useSelector(selectVaultActiveAccount); + const { networkName } = useSelector(selectApiConfigBasedOnActiveNetwork); + + const erc20Balance = + (selectedToken?.balance && + divideErc20Balance( + selectedToken.balance, + selectedToken?.decimals || null + )) || + null; + + const amountForm = useTransferAmountForm( + erc20Balance, + isErc20Transfer, + csprBalance.liquidMotes, + paymentAmount, + selectedToken?.decimals + ); useEffect(() => { const maxAmountMotes: string = @@ -48,32 +84,45 @@ export const AmountStep = ({ amountForm, symbol, isCSPR }: AmountStepProps) => { : Big(csprBalance.liquidMotes).sub(TRANSFER_COST_MOTES).toFixed(); const hasEnoughBalance = Big(maxAmountMotes).gte(TRANSFER_MIN_AMOUNT_MOTES); - const isMaxAmountEqualMinAmount = Big(maxAmountMotes).eq( - TRANSFER_MIN_AMOUNT_MOTES - ); - setIsChecked(isMaxAmountEqualMinAmount); setMaxAmountMotes(maxAmountMotes); setDisabled(!hasEnoughBalance); }, [csprBalance.liquidMotes]); - const { - register, - formState: { errors }, - control, - setValue, - trigger - } = amountForm; + useEffect(() => { + if (!isErc20Transfer) { + setAmount(motesToCSPR(TRANSFER_MIN_AMOUNT_MOTES)); + } + }, [isErc20Transfer, setAmount]); + + const { register, formState, control, trigger, setValue } = amountForm; + + const { errors } = formState; + + useEffect(() => { + if (formState.touchedFields.amount) { + trigger(); + } + }, [ + networkName, + activeAccount?.publicKey, + trigger, + formState.touchedFields.amount, + selectedToken?.amount, + csprBalance.liquidMotes, + paymentAmount + ]); const { onChange: onChangeTransferIdMemo } = register('transferIdMemo'); const { onChange: onChangeCSPRAmount } = register('amount'); + const { onChange: onChangePaymentAmount } = register('paymentAmount'); const amount = useWatch({ control, name: 'amount' }); - const paymentAmount = useWatch({ + const paymentAmountFieldValue = useWatch({ control, name: 'paymentAmount' }); @@ -81,13 +130,20 @@ export const AmountStep = ({ amountForm, symbol, isCSPR }: AmountStepProps) => { const amountLabel = t('Amount'); const transferIdLabel = t('Transfer ID (memo)'); const paymentAmoutLabel = t('Set custom transaction fee'); - const fiatAmount = isCSPR + const fiatAmount = !isErc20Transfer ? formatFiatAmount(amount || '0', currencyRate) : undefined; - const paymentFiatAmount = !isCSPR - ? formatFiatAmount(paymentAmount || '0', currencyRate) + const paymentFiatAmount = isErc20Transfer + ? formatFiatAmount(paymentAmountFieldValue || '0', currencyRate) : undefined; + useEffect(() => { + const isAmountFormButtonDisabled = calculateSubmitButtonDisabled({ + isValid: formState.isValid + }); + setIsAmountFormButtonDisabled(!!isAmountFormButtonDisabled); + }, [formState.isValid, setIsAmountFormButtonDisabled]); + return ( @@ -96,7 +152,7 @@ export const AmountStep = ({ amountForm, symbol, isCSPR }: AmountStepProps) => { - {isCSPR && disabled && ( + {!isErc20Transfer && disabled && ( { monotype type="number" placeholder={t('0.00')} - suffixText={symbol} + suffixText={selectedToken?.symbol} {...register('amount')} - disabled={isCSPR && disabled} + disabled={!isErc20Transfer && disabled} onChange={e => { onChangeCSPRAmount(e); - - if (isChecked) { - setIsChecked(false); - } + setAmount(e.target.value); }} onKeyDown={handleNumericInput} error={!!errors?.amount} @@ -129,29 +182,27 @@ export const AmountStep = ({ amountForm, symbol, isCSPR }: AmountStepProps) => { /> - {isCSPR && ( - - { - if (isChecked) { - setValue('amount', motesToCSPR(TRANSFER_MIN_AMOUNT_MOTES)); - } else { - setValue('amount', motesToCSPR(maxAmountMotes)); - } + {!isErc20Transfer && ( + + { + setAmount(motesToCSPR(maxAmountMotes)); + setValue('amount', motesToCSPR(maxAmountMotes)); + trigger('amount'); - setIsChecked(!isChecked); }} - /> - + > + Send max + + )} {/** transferIdMemo is only relevant for CSPR */} - {isCSPR ? ( + {!isErc20Transfer ? ( { // replace all non-numeric characters e.target.value = e.target.value.replace(/[^0-9]/g, ''); onChangeTransferIdMemo(e); + setTransferIdMemo(e.target.value); }} error={!!errors?.transferIdMemo} validationText={errors?.transferIdMemo?.message} @@ -176,6 +228,10 @@ export const AmountStep = ({ amountForm, symbol, isCSPR }: AmountStepProps) => { suffixText={'CSPR'} {...register('paymentAmount')} error={!!errors?.paymentAmount} + onChange={e => { + onChangePaymentAmount(e); + setPaymentAmount(e.target.value); + }} onKeyDown={handleNumericInput} validationText={ errors?.paymentAmount?.message || @@ -184,6 +240,13 @@ export const AmountStep = ({ amountForm, symbol, isCSPR }: AmountStepProps) => { /> )} + {!isErc20Transfer && ( + + + + )} ); }; diff --git a/src/apps/popup/pages/transfer/components/token-row.tsx b/src/apps/popup/pages/transfer/components/token-row.tsx new file mode 100644 index 000000000..22f6a2fdd --- /dev/null +++ b/src/apps/popup/pages/transfer/components/token-row.tsx @@ -0,0 +1,57 @@ +import React from 'react'; +import styled from 'styled-components'; + +import { TokenType } from '@hooks/use-casper-token'; + +import { + AlignedFlexRow, + AlignedSpaceBetweenFlexRow, + SpacingSize +} from '@libs/layout'; +import { Checkbox, SvgIcon, Typography } from '@libs/ui/components'; + +const Container = styled(AlignedSpaceBetweenFlexRow)` + padding: 16px; + + cursor: pointer; +`; + +const LogoImg = styled.img` + width: 24px; + height: 24px; + + border-radius: ${({ theme }) => theme.borderRadius.hundred}px; +`; + +interface TokenRowProps { + handleSelect: (e: React.MouseEvent) => void; + isSelected: boolean; + token: TokenType; +} + +export const TokenRow = ({ + handleSelect, + isSelected, + token +}: TokenRowProps) => { + const tokenIconFormat = token?.icon?.split('.').pop(); + const isTokenIconJPG = tokenIconFormat === 'jpg'; + + return ( + + + {isTokenIconJPG ? ( + + ) : ( + + )} + {token.name} + + + + ); +}; diff --git a/src/apps/popup/pages/transfer/components/token-swticher-row.tsx b/src/apps/popup/pages/transfer/components/token-swticher-row.tsx new file mode 100644 index 000000000..607496f2d --- /dev/null +++ b/src/apps/popup/pages/transfer/components/token-swticher-row.tsx @@ -0,0 +1,56 @@ +import React from 'react'; +import { Trans, useTranslation } from 'react-i18next'; +import styled from 'styled-components'; + +import { + AlignedFlexRow, + FlexColumn, + ParagraphContainer, + SpaceBetweenFlexRow, + SpacingSize +} from '@libs/layout'; +import { SvgIcon, Tile, Typography } from '@libs/ui/components'; + +const Container = styled(SpaceBetweenFlexRow)` + padding: 16px; +`; + +const RowContainer = styled(FlexColumn)` + width: 100%; +`; + +interface TokenSwitcherRowProps { + tokenName: string | undefined; + iconUrl: string | null | undefined; +} + +export const TokenSwitcherRow = ({ + iconUrl, + tokenName +}: TokenSwitcherRowProps) => { + const { t } = useTranslation(); + + return ( + + + + Token + + + + + + + + + {tokenName} + + + + Change + + + + + ); +}; diff --git a/src/apps/popup/pages/transfer/confirm-step.tsx b/src/apps/popup/pages/transfer/confirm-step.tsx index 1a6e2fbd5..41e915ada 100644 --- a/src/apps/popup/pages/transfer/confirm-step.tsx +++ b/src/apps/popup/pages/transfer/confirm-step.tsx @@ -9,6 +9,7 @@ import { TRANSFER_COST_MOTES } from '@src/constants'; import { selectAccountCurrencyRate } from '@background/redux/account-info/selectors'; import { + AmountContainer, ContentContainer, ParagraphContainer, SpaceBetweenFlexRow, @@ -17,14 +18,13 @@ import { } from '@libs/layout'; import { ActiveAccountPlate, - AmountContainer, List, RecipientPlate, Typography } from '@libs/ui/components'; import { formatFiatAmount, formatNumber, motesToCSPR } from '@libs/ui/utils'; -export const ListItemContainer = styled(SpaceBetweenFlexRow)` +const ListItemContainer = styled(SpaceBetweenFlexRow)` padding: 12px 16px; `; @@ -33,7 +33,7 @@ interface ConfirmStepProps { amount: string; balance: string | null; symbol: string | null; - isCSPR: boolean; + isErc20Transfer: boolean; paymentAmount: string; recipientName?: string; } @@ -42,7 +42,7 @@ export const ConfirmStep = ({ amount, balance, symbol, - isCSPR, + isErc20Transfer, paymentAmount, recipientName }: ConfirmStepProps) => { @@ -51,7 +51,7 @@ export const ConfirmStep = ({ const currencyRate = useSelector(selectAccountCurrencyRate); let transactionDataRows; - if (isCSPR) { + if (!isErc20Transfer) { const transferCostInCSPR = formatNumber(motesToCSPR(TRANSFER_COST_MOTES), { precision: { max: 5 } }); diff --git a/src/apps/popup/pages/transfer/content.tsx b/src/apps/popup/pages/transfer/content.tsx index 5b3d1d7bf..e69de29bb 100644 --- a/src/apps/popup/pages/transfer/content.tsx +++ b/src/apps/popup/pages/transfer/content.tsx @@ -1,76 +0,0 @@ -import React, { useState } from 'react'; -import { UseFormReturn } from 'react-hook-form'; - -import { ILedgerEvent } from '@libs/services/ledger'; -import { LedgerEventView, TransferSuccessScreen } from '@libs/ui/components'; -import { - TransferAmountFormValues, - TransferRecipientFormValues -} from '@libs/ui/forms/transfer'; - -import { AmountStep } from './amount-step'; -import { ConfirmStep } from './confirm-step'; -import { RecipientStep } from './recipient-step'; -import { TransactionSteps } from './utils'; - -interface TransferPageContentProps { - transferStep: TransactionSteps; - recipientPublicKey: string; - amountForm: UseFormReturn; - recipientForm: UseFormReturn; - amount: string; - balance: string | null; - symbol: string | null; - paymentAmount: string; - LedgerEventStatus: ILedgerEvent; -} - -export const TransferPageContent = ({ - transferStep, - recipientPublicKey, - recipientForm, - amountForm, - amount, - balance, - symbol, - paymentAmount, - LedgerEventStatus -}: TransferPageContentProps) => { - const [recipientName, setRecipientName] = useState(''); - - const isCSPR = symbol === 'CSPR'; - - const getContent = { - [TransactionSteps.Recipient]: ( - - ), - [TransactionSteps.Amount]: ( - - ), - [TransactionSteps.Confirm]: ( - - ), - [TransactionSteps.ConfirmWithLedger]: ( - - ), - [TransactionSteps.Success]: ( - - ) - }; - - return getContent[transferStep]; -}; diff --git a/src/apps/popup/pages/transfer/index.tsx b/src/apps/popup/pages/transfer/index.tsx index 0f83091e0..24955ad32 100644 --- a/src/apps/popup/pages/transfer/index.tsx +++ b/src/apps/popup/pages/transfer/index.tsx @@ -2,19 +2,13 @@ import { DeployUtil } from 'casper-js-sdk'; import React, { useEffect, useMemo, useState } from 'react'; import { Trans, useTranslation } from 'react-i18next'; import { useSelector } from 'react-redux'; -import { useParams } from 'react-router-dom'; +import styled from 'styled-components'; -import { - ERC20_PAYMENT_AMOUNT_AVERAGE_MOTES, - TRANSFER_COST_MOTES -} from '@src/constants'; -import { useActiveAccountErc20Tokens } from '@src/hooks/use-active-account-erc20-tokens'; +import { ERC20_PAYMENT_AMOUNT_AVERAGE_MOTES } from '@src/constants'; import { fetchAndDispatchExtendedDeployInfo } from '@src/utils'; -import { TransferPageContent } from '@popup/pages/transfer/content'; import { RouterPath, useTypedLocation, useTypedNavigate } from '@popup/router'; -import { selectAccountBalance } from '@background/redux/account-info/selectors'; import { selectAllPublicKeys } from '@background/redux/contacts/selectors'; import { ledgerDeployChanged, @@ -32,18 +26,21 @@ import { selectVaultActiveAccount } from '@background/redux/vault/selectors'; +import { TokenType, useCasperToken } from '@hooks/use-casper-token'; import { useLedger } from '@hooks/use-ledger'; +import { useSubmitButton } from '@hooks/use-submit-button'; import { createAsymmetricKey } from '@libs/crypto/create-asymmetric-key'; import { AlignedFlexRow, + CenteredFlexRow, ErrorPath, FooterButtonsContainer, HeaderPopup, HeaderSubmenuBarNavLink, PopupLayout, - SpaceBetweenFlexRow, SpacingSize, + VerticalSpaceContainer, createErrorLocationState } from '@libs/layout'; import { @@ -56,30 +53,46 @@ import { HardwareWalletType } from '@libs/types/account'; import { Button, HomePageTabsId, + LedgerEventView, SvgIcon, + TransferSuccessScreen, Typography, renderLedgerFooter } from '@libs/ui/components'; -import { calculateSubmitButtonDisabled } from '@libs/ui/forms/get-submit-button-state-from-validation'; -import { useTransferForm } from '@libs/ui/forms/transfer'; -import { - CSPRtoMotes, - divideErc20Balance, - formatNumber, - motesToCSPR -} from '@libs/ui/utils'; - -import { TransactionSteps, getIsErc20Transfer } from './utils'; +import { CSPRtoMotes, motesToCSPR } from '@libs/ui/utils'; + +import { AmountStep } from './amount-step'; +import { ConfirmStep } from './confirm-step'; +import { RecipientStep } from './recipient-step'; +import { TokenStep } from './token-step'; +import { TransactionSteps } from './utils'; + +const ScrollContainer = styled(VerticalSpaceContainer)<{ + isHidden: boolean; +}>` + opacity: ${({ isHidden }) => (isHidden ? '0' : '1')}; + height: ${({ isHidden }) => (isHidden ? '0' : '24px')}; + visibility: ${({ isHidden }) => (isHidden ? 'hidden' : 'visible')}; + transition: + opacity 0.2s ease-in-out, + height 0.5s ease-in-out; +`; + +const ConfirmButtonContainer = styled(FooterButtonsContainer)<{ + isHidden: boolean; +}>` + gap: ${({ isHidden }) => (isHidden ? '0' : '16px')}; + transition: gap 0.5s ease-in-out; +`; export const TransferPage = () => { const { t } = useTranslation(); const navigate = useTypedNavigate(); const location = useTypedLocation(); - const { tokenContractPackageHash, tokenContractHash } = useParams(); - - const isErc20Transfer = getIsErc20Transfer(tokenContractPackageHash); - + const [isErc20Transfer, setIsErc20Transfer] = useState(false); + const [selectedToken, setSelectedToken] = useState(); + const [recipientName, setRecipientName] = useState(''); const [recipientPublicKey, setRecipientPublicKey] = useState(''); const [amount, setAmount] = useState(''); const [paymentAmount, setPaymentAmount] = useState( @@ -87,9 +100,12 @@ export const TransferPage = () => { ); const [transferIdMemo, setTransferIdMemo] = useState(''); const [transferStep, setTransferStep] = useState( - TransactionSteps.Recipient + TransactionSteps.Token ); - const [isSubmitButtonDisable, setIsSubmitButtonDisable] = useState(true); + const [isRecipientFormButtonDisabled, setIsRecipientFormButtonDisabled] = + useState(true); + const [isAmountFormButtonDisabled, setIsAmountFormButtonDisabled] = + useState(false); const activeAccount = useSelector(selectVaultActiveAccount); const isActiveAccountFromLedger = useSelector( @@ -98,104 +114,50 @@ export const TransferPage = () => { const { networkName, nodeUrl } = useSelector( selectApiConfigBasedOnActiveNetwork ); - const csprBalance = useSelector(selectAccountBalance); const contactPublicKeys = useSelector(selectAllPublicKeys); const ratedInStore = useSelector(selectRatedInStore); const askForReviewAfter = useSelector(selectAskForReviewAfter); - const { tokens } = useActiveAccountErc20Tokens(); - - const token = tokens?.find(token => token.id === tokenContractPackageHash); - const symbol = isErc20Transfer - ? token?.symbol || location.state?.tokenData?.symbol || null - : 'CSPR'; - const erc20Decimals = - token?.decimals || location.state?.tokenData?.decimals || null; - const erc20Balance = - (token?.balance && divideErc20Balance(token?.balance, erc20Decimals)) || - null; - const balance = isErc20Transfer - ? erc20Balance - : csprBalance.liquidMotes && motesToCSPR(csprBalance.liquidMotes); - const formattedBalance = formatNumber(balance || '', { - precision: { max: 5 } - }); - const isRecipientPublicKeyInContact = useMemo( - () => contactPublicKeys.includes(recipientPublicKey), - [contactPublicKeys, recipientPublicKey] - ); + const casperToken = useCasperToken(); - const { amountForm, recipientForm } = useTransferForm( - erc20Balance, - isErc20Transfer, - csprBalance.liquidMotes, - paymentAmount - ); + useEffect(() => { + const tokenData = location?.state?.tokenData; - const { - formState: amountFormState, - getValues: getValuesAmountForm, - trigger - } = amountForm; - const { formState: recipientFormState, getValues: getValuesRecipientForm } = - recipientForm; + if (selectedToken) { + return; + } - useEffect(() => { - if (amountFormState.touchedFields.amount) { - trigger(); + if (tokenData) { + if (tokenData.name === 'Casper') { + setIsErc20Transfer(false); + } else { + setIsErc20Transfer(true); + } + setSelectedToken(tokenData); + } else { + setIsErc20Transfer(false); + setSelectedToken(casperToken); } - }, [ - networkName, - activeAccount?.publicKey, - trigger, - amountFormState.touchedFields.amount, - erc20Balance, - csprBalance.liquidMotes, - paymentAmount - ]); - - // event listener for enable/disable submit button + }, [casperToken, location?.state?.tokenData, selectedToken]); + useEffect(() => { - const layoutContentContainer = document.querySelector('#ms-container'); - - // if the content is not scrollable, we can enable the submit button - if ( - layoutContentContainer && - layoutContentContainer.clientHeight === - layoutContentContainer.scrollHeight && - transferStep === TransactionSteps.Confirm && - isSubmitButtonDisable - ) { - setIsSubmitButtonDisable(false); + if (isErc20Transfer) { + setIsAmountFormButtonDisabled(false); + } else { + setIsAmountFormButtonDisabled(true); } + }, [isErc20Transfer, setIsAmountFormButtonDisabled]); - const handleScroll = () => { - if ( - layoutContentContainer && - transferStep === TransactionSteps.Confirm && - isSubmitButtonDisable - ) { - const bottom = - Math.ceil( - layoutContentContainer.clientHeight + - layoutContentContainer.scrollTop - ) >= layoutContentContainer.scrollHeight; - - if (bottom) { - // we are at the bottom of the page - setIsSubmitButtonDisable(false); - } - } - }; - - // add event listener to the scrollable container - layoutContentContainer?.addEventListener('scroll', handleScroll); + const isRecipientPublicKeyInContact = useMemo( + () => contactPublicKeys.includes(recipientPublicKey), + [contactPublicKeys, recipientPublicKey] + ); - // remove event listener on cleanup - return () => { - layoutContentContainer?.removeEventListener('scroll', handleScroll); - }; - }, [isSubmitButtonDisable, transferStep]); + const { + isSubmitButtonDisable, + setIsSubmitButtonDisable, + isAdditionalTextVisible + } = useSubmitButton(transferStep === TransactionSteps.Confirm); const sendDeploy = (signDeploy: DeployUtil.Deploy) => { sendSignDeploy(signDeploy, nodeUrl) @@ -243,6 +205,8 @@ export const TransferPage = () => { }; const onSubmitSending = async () => { + setIsSubmitButtonDisable(true); + if (activeAccount) { const KEYS = createAsymmetricKey( activeAccount.publicKey, @@ -254,11 +218,11 @@ export const TransferPage = () => { const deploy = await makeCep18TransferDeploy( nodeUrl, networkName, - tokenContractHash, - tokenContractPackageHash, + selectedToken?.contractHash, + selectedToken?.id, recipientPublicKey, amount, - erc20Decimals, + selectedToken?.decimals || null, paymentAmount, activeAccount ); @@ -294,11 +258,11 @@ export const TransferPage = () => { const deploy = await makeCep18TransferDeploy( nodeUrl, networkName, - tokenContractHash, - tokenContractPackageHash, + selectedToken?.contractHash, + selectedToken?.id, recipientPublicKey, amount, - erc20Decimals, + selectedToken?.decimals || null, paymentAmount, activeAccount ); @@ -336,57 +300,180 @@ export const TransferPage = () => { beforeLedgerActionCb }); - const getButtonProps = () => { - const isRecipientFormButtonDisabled = calculateSubmitButtonDisabled({ - isValid: recipientFormState.isValid - }); - const isAmountFormButtonDisabled = calculateSubmitButtonDisabled({ - isValid: amountFormState.isValid - }); - - switch (transferStep) { - case TransactionSteps.Recipient: { - return { - disabled: isRecipientFormButtonDisabled, - onClick: () => { - const { recipientPublicKey } = getValuesRecipientForm(); + const content = { + [TransactionSteps.Token]: ( + + ), + [TransactionSteps.Recipient]: ( + + ), + [TransactionSteps.Amount]: ( + + ), + [TransactionSteps.ConfirmWithLedger]: ( + + ), + [TransactionSteps.Confirm]: ( + + ), + [TransactionSteps.Success]: ( + + ) + }; + + const headerButtons = { + [TransactionSteps.Token]: ( + + ), + [TransactionSteps.Recipient]: ( + setTransferStep(TransactionSteps.Token)} + /> + ), + [TransactionSteps.Amount]: ( + setTransferStep(TransactionSteps.Recipient)} + /> + ), + [TransactionSteps.Confirm]: ( + setTransferStep(TransactionSteps.Amount)} + /> + ), + [TransactionSteps.ConfirmWithLedger]: ( + setTransferStep(TransactionSteps.Confirm)} + /> + ) + }; + + const ledgerFooterButton = renderLedgerFooter({ + onConnect: makeSubmitLedgerAction, + event: ledgerEventStatusToRender, + onErrorCtaPressed: () => { + setTransferStep(TransactionSteps.Confirm); + } + }); + const footerButtons = { + [TransactionSteps.Token]: ( + + + + ), + [TransactionSteps.Recipient]: ( + + + + ), + [TransactionSteps.Amount]: ( + + + + ), + [TransactionSteps.ConfirmWithLedger]: ledgerFooterButton ? ( + ledgerFooterButton() + ) : ( + <> + ), + [TransactionSteps.Confirm]: ( + + + + + Scroll down to check all details + + + + + + ), + [TransactionSteps.Success]: ( + + - {transferStep === TransactionSteps.Success && - !isRecipientPublicKeyInContact && ( - + {!isRecipientPublicKeyInContact && ( + - )} - - ); - }; + }) + } + > + Add recipient to list of contacts + + )} + + ) }; return ( @@ -513,23 +522,15 @@ export const TransferPage = () => { withNetworkSwitcher withMenu withConnectionStatus - renderSubmenuBarItems={getBackButton[transferStep]} - /> - )} - renderContent={() => ( - headerButtons[transferStep] + } /> )} - renderFooter={renderFooter()} + renderContent={() => content[transferStep]} + renderFooter={() => footerButtons[transferStep]} /> ); }; diff --git a/src/apps/popup/pages/transfer/recipient-step.tsx b/src/apps/popup/pages/transfer/recipient-step.tsx index 12ec13f9e..d0a50e584 100644 --- a/src/apps/popup/pages/transfer/recipient-step.tsx +++ b/src/apps/popup/pages/transfer/recipient-step.tsx @@ -1,5 +1,4 @@ -import React from 'react'; -import { UseFormReturn } from 'react-hook-form'; +import React, { useEffect } from 'react'; import { Trans, useTranslation } from 'react-i18next'; import { @@ -7,30 +6,40 @@ import { ParagraphContainer, SpacingSize } from '@libs/layout'; -import { - ActiveAccountPlate, - RecipientDropdownInput, - Typography -} from '@libs/ui/components'; -import { TransferRecipientFormValues } from '@libs/ui/forms/transfer'; +import { Typography } from '@libs/ui/components'; +import { RecipientTabs } from '@libs/ui/components/recipient-tabs/recipient-tabs'; +import { calculateSubmitButtonDisabled } from '@libs/ui/forms/get-submit-button-state-from-validation'; +import { useTransferRecipientForm } from '@libs/ui/forms/transfer'; interface RecipientStepProps { - recipientForm: UseFormReturn; - balance: string | null; - symbol: string | null; setRecipientName: React.Dispatch>; recipientName: string; + setRecipientPublicKey: (value: React.SetStateAction) => void; + setIsRecipientFormButtonDisabled: React.Dispatch< + React.SetStateAction + >; } export const RecipientStep = ({ - recipientForm, - balance, - symbol, setRecipientName, - recipientName + recipientName, + setIsRecipientFormButtonDisabled, + setRecipientPublicKey }: RecipientStepProps) => { const { t } = useTranslation(); + const recipientForm = useTransferRecipientForm(); + + const { formState } = recipientForm; + + useEffect(() => { + const isRecipientFormButtonDisabled = calculateSubmitButtonDisabled({ + isValid: formState.isValid + }); + + setIsRecipientFormButtonDisabled(!!isRecipientFormButtonDisabled); + }, [formState.isValid, setIsRecipientFormButtonDisabled]); + return ( @@ -38,12 +47,12 @@ export const RecipientStep = ({ Specify recipient - - ); diff --git a/src/apps/popup/pages/transfer/token-step.tsx b/src/apps/popup/pages/transfer/token-step.tsx new file mode 100644 index 000000000..7e3e80ad9 --- /dev/null +++ b/src/apps/popup/pages/transfer/token-step.tsx @@ -0,0 +1,110 @@ +import React, { useEffect, useState } from 'react'; +import { Trans, useTranslation } from 'react-i18next'; + +import { useActiveAccountErc20Tokens } from '@hooks/use-active-account-erc20-tokens'; +import { TokenType, useCasperToken } from '@hooks/use-casper-token'; + +import { + ContentContainer, + ParagraphContainer, + SpacingSize +} from '@libs/layout'; +import { + ActiveAccountPlate, + List, + Modal, + ModalSwitcher, + Typography +} from '@libs/ui/components'; + +import { TokenRow } from './components/token-row'; +import { TokenSwitcherRow } from './components/token-swticher-row'; + +interface TokenStepProps { + setSelectedToken: React.Dispatch< + React.SetStateAction + >; + setIsErc20Transfer: (value: React.SetStateAction) => void; + selectedToken: TokenType | null | undefined; +} + +export const TokenStep = ({ + setSelectedToken, + setIsErc20Transfer, + selectedToken +}: TokenStepProps) => { + const [tokenList, setTokenList] = useState([]); + const { t } = useTranslation(); + + const casperToken = useCasperToken(); + const { tokens } = useActiveAccountErc20Tokens(); + + useEffect(() => { + const tokensList: TokenType[] = []; + + if (casperToken) { + tokensList.push(casperToken); + } + + if (tokens) { + tokensList.push(...tokens); + } + + setTokenList(tokensList); + }, [casperToken, tokens]); + + return ( + + + + Select token and account + + + + ( + + { + const isSelected = token.name === selectedToken?.name; + + return ( + { + setSelectedToken(token); + if (token.name === 'Casper') { + setIsErc20Transfer(false); + } else { + setIsErc20Transfer(true); + } + closeModal(e); + }} + /> + ); + }} + marginLeftForItemSeparatorLine={54} + /> + + )} + placement="fullBottom" + children={() => ( + + )} + /> + + + + ); +}; diff --git a/src/apps/popup/pages/transfer/utils.ts b/src/apps/popup/pages/transfer/utils.ts index 4a6653c12..1d107332e 100644 --- a/src/apps/popup/pages/transfer/utils.ts +++ b/src/apps/popup/pages/transfer/utils.ts @@ -1,15 +1,8 @@ export enum TransactionSteps { + Token = 'token', Recipient = 'recipient', Amount = 'amount', Confirm = 'confirm', ConfirmWithLedger = 'confirm with ledger', Success = 'success' } - -export const getIsErc20Transfer = ( - tokenContractPackageHash: string | undefined -) => { - return ( - tokenContractPackageHash != null && tokenContractPackageHash !== 'Casper' - ); -}; diff --git a/src/apps/popup/pages/wallet-qr-code/index.tsx b/src/apps/popup/pages/wallet-qr-code/index.tsx index 50be011e8..05b585230 100644 --- a/src/apps/popup/pages/wallet-qr-code/index.tsx +++ b/src/apps/popup/pages/wallet-qr-code/index.tsx @@ -10,18 +10,23 @@ import { selectVaultImportedAccounts } from '@background/redux/vault/selectors'; -import { generateSyncWalletQrData } from '@libs/crypto'; import { HeaderPopup, HeaderSubmenuBarNavLink, PopupLayout } from '@libs/layout'; +interface GenerateWalletQrDataMessageEvent extends MessageEvent { + data: { + result: string[]; + }; +} + export const WalletQrCodePage = () => { const [qrStrings, setQrStrings] = useState([]); const [isPasswordConfirmed, setIsPasswordConfirmed] = useState(false); - const [loading, setLoading] = useState(false); + const [isLoading, setIsLoading] = useState(false); const secretPhrase = useSelector(selectSecretPhrase); const derivedAccounts = useSelector(selectVaultDerivedAccounts, shallowEqual); @@ -36,26 +41,39 @@ export const WalletQrCodePage = () => { const generateQRCode = async (password: string) => { if (secretPhrase) { - setLoading(true); - const data = await generateSyncWalletQrData( + setIsLoading(true); + + const worker = new Worker( + new URL( + '@background/workers/generate-sync-wallet-qr-data-worker.ts', + import.meta.url + ) + ); + + worker.postMessage({ password, secretPhrase, derivedAccounts, importedAccounts - ); + }); + + worker.onmessage = (event: GenerateWalletQrDataMessageEvent) => { + const { result } = event.data; + + setQrStrings(result); + setPasswordConfirmed(); + }; - setLoading(false); - setQrStrings(data); + worker.onerror = error => { + console.error(error); + setIsLoading(false); + }; } }; if (!isPasswordConfirmed) { return ( - + ); } diff --git a/src/apps/popup/router/paths.ts b/src/apps/popup/router/paths.ts index 9558b75ad..4778a5822 100644 --- a/src/apps/popup/router/paths.ts +++ b/src/apps/popup/router/paths.ts @@ -13,8 +13,7 @@ export enum RouterPath { ConnectedSites = '/connected-sites', BackupSecretPhrase = '/backup-secret-phrase', DownloadAccountKeys = '/download-account-keys', - Transfer = '/transfer/:tokenContractPackageHash/:tokenContractHash', - TransferNoParams = '/transfer', + Transfer = '/transfer', ActivityDetails = '/activity-details', Token = '/token/:tokenName', Receive = '/receive', diff --git a/src/apps/popup/router/types.ts b/src/apps/popup/router/types.ts index 795020a8e..1d624bf6a 100644 --- a/src/apps/popup/router/types.ts +++ b/src/apps/popup/router/types.ts @@ -2,7 +2,7 @@ import { ActivityType } from '@src/constants'; import { TokenType } from '@hooks/use-casper-token'; -import { ErrorLocationState } from '@libs/layout'; +import { ErrorLocationState } from '@libs/layout/error/types'; export interface LocationState extends ErrorLocationState { showNavigationMenu?: boolean; diff --git a/src/assets/icons/paste.svg b/src/assets/icons/paste.svg new file mode 100644 index 000000000..d5f0d8c07 --- /dev/null +++ b/src/assets/icons/paste.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/background/index.ts b/src/background/index.ts index 3c2fd6513..ec02045d3 100644 --- a/src/background/index.ts +++ b/src/background/index.ts @@ -79,12 +79,13 @@ import { accountImported, accountRemoved, accountRenamed, + accountsAdded, accountsImported, activeAccountChanged, anotherAccountConnected, deployPayloadReceived, deploysReseted, - hideAccountFromListChange, + hideAccountFromListChanged, secretPhraseCreated, siteConnected, siteDisconnected, @@ -158,11 +159,11 @@ import { recipientPublicKeyReseted } from './redux/recent-recipient-public-keys/actions'; import { - changePassword, createAccount, initKeys, initVault, lockVault, + recoverVault, resetVault, unlockVault } from './redux/sagas/actions'; @@ -573,6 +574,7 @@ runtime.onMessage.addListener( case getType(unlockVault): case getType(initKeys): case getType(initVault): + case getType(recoverVault): case getType(createAccount): case getType(deploysReseted): case getType(sessionReseted): @@ -584,10 +586,11 @@ runtime.onMessage.addListener( case getType(accountImported): case getType(accountsImported): case getType(accountAdded): + case getType(accountsAdded): case getType(accountRemoved): case getType(accountRenamed): case getType(activeAccountChanged): - case getType(hideAccountFromListChange): + case getType(hideAccountFromListChanged): case getType(activeTimeoutDurationSettingChanged): case getType(activeNetworkSettingChanged): case getType(vaultSettingsReseted): @@ -632,7 +635,6 @@ runtime.onMessage.addListener( case getType(accountCasperActivityCountChanged): case getType(accountTrackingIdOfSentNftTokensChanged): case getType(accountTrackingIdOfSentNftTokensRemoved): - case getType(changePassword): case getType(newContactAdded): case getType(contactRemoved): case getType(contactEditingPermissionChanged): diff --git a/src/background/open-onboarding-flow.ts b/src/background/open-onboarding-flow.ts index 19e38ab60..3ee4ccd1f 100644 --- a/src/background/open-onboarding-flow.ts +++ b/src/background/open-onboarding-flow.ts @@ -51,7 +51,6 @@ export async function openOnboardingUi() { // create a tab if it does not exist or if it's not an onboarding URL if (!tabExist || !isOnboardingUrl) { - console.log(123); tabs .create({ url: 'onboarding.html', active: true }) .then(tab => { diff --git a/src/background/redux/contacts/types.ts b/src/background/redux/contacts/types.ts index b2ff728af..b753540bf 100644 --- a/src/background/redux/contacts/types.ts +++ b/src/background/redux/contacts/types.ts @@ -12,3 +12,7 @@ export type ContactsState = { export interface EditContactActionType extends Contact { oldName: string; } + +export interface ContactWithId extends Contact { + id: string; +} diff --git a/src/background/redux/sagas/actions.ts b/src/background/redux/sagas/actions.ts index 08134a113..663e14f2b 100644 --- a/src/background/redux/sagas/actions.ts +++ b/src/background/redux/sagas/actions.ts @@ -6,6 +6,7 @@ import { createAction } from 'typesafe-actions'; import { UnlockVault } from '@background/redux/sagas/types'; import { SecretPhrase } from '@libs/crypto'; +import { Account } from '@libs/types/account'; export const startBackground = createAction('START_BACKGROUND_SAGA')(); @@ -23,10 +24,11 @@ export const initVault = createAction('INIT_VAULT_SAGA')<{ secretPhrase: SecretPhrase; }>(); -export const createAccount = createAction('CREATE_ACCOUNT_SAGA')<{ - name?: string; +export const recoverVault = createAction('RECOVER_VAULT_SAGA')<{ + secretPhrase: SecretPhrase; + accounts: Account[]; }>(); -export const changePassword = createAction('CHANGE_PASSWORD_SAGA')<{ - password: string; +export const createAccount = createAction('CREATE_ACCOUNT_SAGA')<{ + name?: string; }>(); diff --git a/src/background/redux/sagas/onboarding-sagas.ts b/src/background/redux/sagas/onboarding-sagas.ts index 558276936..efd071518 100644 --- a/src/background/redux/sagas/onboarding-sagas.ts +++ b/src/background/redux/sagas/onboarding-sagas.ts @@ -26,16 +26,18 @@ import { sagaCall } from '../utils'; import { vaultCipherReseted } from '../vault-cipher/actions'; import { accountAdded, + accountsAdded, deploysReseted, secretPhraseCreated, vaultReseted } from '../vault/actions'; -import { initKeys, initVault, resetVault } from './actions'; +import { initKeys, initVault, recoverVault, resetVault } from './actions'; export function* onboardingSagas() { yield takeLatest(getType(resetVault), resetVaultSaga); yield takeLatest(getType(initKeys), initKeysSage); yield takeLatest(getType(initVault), initVaultSaga); + yield takeLatest(getType(recoverVault), recoverVaultSaga); } /** @@ -117,3 +119,20 @@ function* initVaultSaga(action: ReturnType) { console.error(err); } } + +function* recoverVaultSaga(action: ReturnType) { + try { + const { secretPhrase, accounts } = action.payload; + if (!validateSecretPhrase(secretPhrase)) { + throw Error('Invalid secret phrase.'); + } + + yield put(secretPhraseCreated(secretPhrase)); + yield put(accountsAdded(accounts)); + yield put(vaultUnlocked()); + // cleanup and disabling action handler + disableOnboardingFlow(); + } catch (err) { + console.error(err); + } +} diff --git a/src/background/redux/sagas/vault-sagas.ts b/src/background/redux/sagas/vault-sagas.ts index 1df56ac45..15d8dcee4 100644 --- a/src/background/redux/sagas/vault-sagas.ts +++ b/src/background/redux/sagas/vault-sagas.ts @@ -19,13 +19,8 @@ import { emitSdkEventToActiveTabs } from '@background/utils'; import { sdkEvent } from '@content/sdk-event'; import { deriveKeyPair } from '@libs/crypto'; -import { - deriveEncryptionKey, - encodePassword, - generateRandomSaltHex -} from '@libs/crypto/hashing'; -import { convertBytesToHex } from '@libs/crypto/utils'; import { encryptVault } from '@libs/crypto/vault'; +import { Account } from '@libs/types/account'; import { accountInfoReset } from '../account-info/actions'; import { keysUpdated } from '../keys/actions'; @@ -52,11 +47,13 @@ import { accountImported, accountRemoved, accountRenamed, + accountsAdded, accountsImported, activeAccountChanged, anotherAccountConnected, deployPayloadReceived, deploysReseted, + hideAccountFromListChanged, siteConnected, siteDisconnected, vaultLoaded, @@ -71,7 +68,6 @@ import { } from '../vault/selectors'; import { popupWindowInit } from '../windowManagement/actions'; import { - changePassword, createAccount, lockVault, startBackground, @@ -96,6 +92,7 @@ export function* vaultSagas() { yield takeLatest( [ getType(accountAdded), + getType(accountsAdded), getType(accountImported), getType(accountsImported), getType(accountRemoved), @@ -106,12 +103,12 @@ export function* vaultSagas() { getType(siteDisconnected), getType(activeAccountChanged), getType(activeTimeoutDurationSettingChanged), - getType(deployPayloadReceived) + getType(deployPayloadReceived), + getType(hideAccountFromListChanged) ], updateVaultCipher ); yield takeLatest(getType(createAccount), createAccountSaga); - yield takeLatest(getType(changePassword), changePasswordSaga); } /** @@ -316,59 +313,31 @@ function* createAccountSaga(action: ReturnType) { throw Error('Account name exist'); } - const accountCount = derivedAccounts.length; - const secretPhrase = yield* sagaSelect(selectSecretPhrase); - - const keyPair = deriveKeyPair(secretPhrase, accountCount); - const account = { - ...keyPair, - name, - hidden: false - }; - - yield put(accountAdded(account)); - } catch (err) { - console.error(err); - } -} - -function* changePasswordSaga(action: ReturnType) { - try { - const { password } = action.payload; - - const passwordSaltHash = generateRandomSaltHex(); - const passwordHash = yield* sagaCall(() => - encodePassword(password, passwordSaltHash) - ); - const keyDerivationSaltHash = generateRandomSaltHex(); - const newEncryptionKeyBytes = yield* sagaCall(() => - deriveEncryptionKey(password, keyDerivationSaltHash) - ); - const newEncryptionKeyHash = convertBytesToHex(newEncryptionKeyBytes); - - yield put( - keysUpdated({ - passwordHash, - passwordSaltHash, - keyDerivationSaltHash - }) - ); - yield put( - encryptionKeyHashCreated({ encryptionKeyHash: newEncryptionKeyHash }) - ); - - const vault = yield* sagaSelect(selectVault); + let isAccountAlreadyAdded = true; + let i = 0; - // encrypt cipher with the new key - const newVaultCipher = yield* sagaCall(() => - encryptVault(newEncryptionKeyHash, vault) - ); + const secretPhrase = yield* sagaSelect(selectSecretPhrase); - yield put( - vaultCipherCreated({ - vaultCipher: newVaultCipher - }) - ); + while (isAccountAlreadyAdded) { + const keyPair = deriveKeyPair(secretPhrase, i); + if ( + !derivedAccounts.some( + account => account.publicKey === keyPair.publicKey + ) + ) { + isAccountAlreadyAdded = false; + + const account: Account = { + ...keyPair, + name, + hidden: false, + derivationIndex: i + }; + yield put(accountAdded(account)); + break; + } + i++; + } } catch (err) { console.error(err); } diff --git a/src/background/redux/vault/actions.ts b/src/background/redux/vault/actions.ts index 44c68bb16..2458e02e2 100644 --- a/src/background/redux/vault/actions.ts +++ b/src/background/redux/vault/actions.ts @@ -17,6 +17,8 @@ export const accountImported = createAction('ACCOUNT_IMPORTED')(); export const accountAdded = createAction('ACCOUNT_ADDED')(); +export const accountsAdded = createAction('ACCOUNTS_ADDED')(); + export const accountsImported = createAction('ACCOUNTS_IMPORTED')(); export const accountRemoved = createAction('ACCOUNT_REMOVED')<{ @@ -61,8 +63,8 @@ export const deployPayloadReceived = createAction('DEPLOY_PAYLOAD_RECEIVED')<{ json: string; }>(); -export const hideAccountFromListChange = createAction( - 'HIDE_ACCOUNT_FROM_LIST_CHANGE' +export const hideAccountFromListChanged = createAction( + 'HIDE_ACCOUNT_FROM_LIST_CHANGED' )<{ accountName: string; }>(); diff --git a/src/background/redux/vault/reducer.ts b/src/background/redux/vault/reducer.ts index 5c1d41954..c8bc9d4d1 100644 --- a/src/background/redux/vault/reducer.ts +++ b/src/background/redux/vault/reducer.ts @@ -6,12 +6,13 @@ import { accountImported, accountRemoved, accountRenamed, + accountsAdded, accountsImported, activeAccountChanged, anotherAccountConnected, deployPayloadReceived, deploysReseted, - hideAccountFromListChange, + hideAccountFromListChanged, secretPhraseCreated, siteConnected, siteDisconnected, @@ -88,6 +89,15 @@ export const reducer = createReducer(initialState) state.accounts.length === 0 ? account.name : state.activeAccountName }) ) + .handleAction( + accountsAdded, + (state, { payload: accounts }: ReturnType) => ({ + ...state, + accounts: [...state.accounts, ...accounts], + activeAccountName: + state.accounts.length === 0 ? accounts[0].name : state.activeAccountName + }) + ) .handleAction( accountsImported, (state, { payload: accounts }: ReturnType) => ({ @@ -277,10 +287,12 @@ export const reducer = createReducer(initialState) }) ) .handleAction( - hideAccountFromListChange, + hideAccountFromListChanged, ( state, - { payload: { accountName } }: ReturnType + { + payload: { accountName } + }: ReturnType ) => { const visibleAccounts = state.accounts.filter( account => !account.hidden && account.name !== accountName diff --git a/src/background/workers/create-password-worker.ts b/src/background/workers/create-password-worker.ts new file mode 100644 index 000000000..96f1c9fcf --- /dev/null +++ b/src/background/workers/create-password-worker.ts @@ -0,0 +1,39 @@ +import { VaultState } from '@background/redux/vault/types'; + +import { + deriveEncryptionKey, + encodePassword, + generateRandomSaltHex +} from '@libs/crypto/hashing'; +import { convertBytesToHex } from '@libs/crypto/utils'; +import { encryptVault } from '@libs/crypto/vault'; + +interface CreatePasswordWorkerEvent extends MessageEvent { + data: { + password: string; + vault: VaultState; + }; +} + +onmessage = async function (event: CreatePasswordWorkerEvent) { + const { password, vault } = event.data; + + const passwordSaltHash = generateRandomSaltHex(); + const passwordHash = await encodePassword(password, passwordSaltHash); + const keyDerivationSaltHash = generateRandomSaltHex(); + const newEncryptionKeyBytes = await deriveEncryptionKey( + password, + keyDerivationSaltHash + ); + const newEncryptionKeyHash = convertBytesToHex(newEncryptionKeyBytes); + + const newVaultCipher = await encryptVault(newEncryptionKeyHash, vault); + + postMessage({ + passwordHash, + passwordSaltHash, + newEncryptionKeyHash, + keyDerivationSaltHash, + newVaultCipher + }); +}; diff --git a/src/libs/crypto/sync-wallet-qr.ts b/src/background/workers/generate-sync-wallet-qr-data-worker.ts similarity index 68% rename from src/libs/crypto/sync-wallet-qr.ts rename to src/background/workers/generate-sync-wallet-qr-data-worker.ts index 5c5179b71..14f45ca0d 100644 --- a/src/libs/crypto/sync-wallet-qr.ts +++ b/src/background/workers/generate-sync-wallet-qr-data-worker.ts @@ -3,17 +3,23 @@ import { scryptAsync } from '@noble/hashes/scrypt'; import { randomBytes } from '@noble/hashes/utils'; import { CLPublicKey } from 'casper-js-sdk'; +import { createScryptOptions } from '@libs/crypto/hashing'; +import { convertBytesToBase64 } from '@libs/crypto/utils'; import { Account } from '@libs/types/account'; -import { createScryptOptions } from './hashing'; -import { convertBytesToBase64 } from './utils'; +interface GenerateSyncWalletQrDataEvent extends MessageEvent { + data: { + password: string; + secretPhrase: string[]; + derivedAccounts: Account[]; + importedAccounts: Account[]; + }; +} + +onmessage = async function (event: GenerateSyncWalletQrDataEvent) { + const { password, secretPhrase, derivedAccounts, importedAccounts } = + event.data; -export const generateSyncWalletQrData = async ( - password: string, - secretPhrase: string[], - derivedAccounts: Account[], - importedAccounts: Account[] -) => { const salt = randomBytes(16); const iv = randomBytes(16); @@ -46,7 +52,11 @@ export const generateSyncWalletQrData = async ( const qrData = convertBytesToBase64(qrBytes); const qrDataArray = qrData.match(/.{1,200}/g); - return (qrDataArray ?? [qrData]).map( + const result = (qrDataArray ?? [qrData]).map( (qr, i, arr) => `${i + 1}$${arr.length}$${qr}` ); + + postMessage({ + result + }); }; diff --git a/src/background/workers/unlockVaultWorker.ts b/src/background/workers/unlock-vault-worker.ts similarity index 100% rename from src/background/workers/unlockVaultWorker.ts rename to src/background/workers/unlock-vault-worker.ts diff --git a/src/background/workers/verify-password-worker.ts b/src/background/workers/verify-password-worker.ts new file mode 100644 index 000000000..7b49d8aa6 --- /dev/null +++ b/src/background/workers/verify-password-worker.ts @@ -0,0 +1,22 @@ +import { verifyPasswordAgainstHash } from '@libs/crypto/hashing'; + +interface VerifyPasswordEvent extends MessageEvent { + data: { + passwordHash: string; + passwordSaltHash: string; + password: string; + }; +} + +onmessage = async (event: VerifyPasswordEvent) => { + const { passwordHash, passwordSaltHash, password } = event.data; + const isPasswordCorrect = await verifyPasswordAgainstHash( + passwordHash, + passwordSaltHash, + password + ); + + postMessage({ + isPasswordCorrect + }); +}; diff --git a/src/constants.ts b/src/constants.ts index 8047aae37..3fe79094c 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -15,6 +15,7 @@ export const ERC20_TOKEN_ACTIVITY_REFRESH_RATE = 30 * SECOND; export const VALIDATORS_REFRESH_RATE = 30 * SECOND; export const LOGIN_RETRY_ATTEMPTS_LIMIT = 5; +export const ERROR_DISPLAYED_BEFORE_ATTEMPT_IS_DECREMENTED = 1; export const MOTES_PER_CSPR_RATE = '1000000000'; // 1 000 000 000 MOTES === 1 CSPR export const TRANSFER_COST_MOTES = '100000000'; // 0.1 CSPR diff --git a/src/hooks/use-submit-button.ts b/src/hooks/use-submit-button.ts new file mode 100644 index 000000000..68a994e16 --- /dev/null +++ b/src/hooks/use-submit-button.ts @@ -0,0 +1,51 @@ +import { useEffect, useState } from 'react'; + +export const useSubmitButton = (isConfirmStep: boolean) => { + const [isSubmitButtonDisable, setIsSubmitButtonDisable] = useState(true); + const [isAdditionalTextVisible, setIsAdditionalTextVisible] = useState(true); + + useEffect(() => { + if (!isConfirmStep) return; + + const layoutContentContainer = document.querySelector('#ms-container'); + + // if the content is not scrollable, we can enable the submit button + if ( + layoutContentContainer && + layoutContentContainer.clientHeight === + layoutContentContainer.scrollHeight + ) { + setIsSubmitButtonDisable(false); + setIsAdditionalTextVisible(false); + } + + const handleScroll = () => { + if (layoutContentContainer) { + const bottom = + Math.ceil( + layoutContentContainer.clientHeight + + layoutContentContainer.scrollTop + ) >= layoutContentContainer.scrollHeight; + + if (bottom) { + setIsSubmitButtonDisable(false); + setIsAdditionalTextVisible(false); + } + } + }; + + // add event listener to the scrollable container + layoutContentContainer?.addEventListener('scroll', handleScroll); + + // remove event listener on cleanup + return () => { + layoutContentContainer?.removeEventListener('scroll', handleScroll); + }; + }, [isConfirmStep]); + + return { + isSubmitButtonDisable, + setIsSubmitButtonDisable, + isAdditionalTextVisible + }; +}; diff --git a/src/libs/crypto/index.ts b/src/libs/crypto/index.ts index 95030aebf..b7ec89731 100644 --- a/src/libs/crypto/index.ts +++ b/src/libs/crypto/index.ts @@ -4,4 +4,3 @@ export * from './bip39'; export * from './bip44'; export * from './parse-secret-key-string'; export * from './sign-deploy'; -export * from './sync-wallet-qr'; diff --git a/src/libs/layout/containers.ts b/src/libs/layout/containers.ts index 11a224820..b1cd25ec4 100644 --- a/src/libs/layout/containers.ts +++ b/src/libs/layout/containers.ts @@ -203,16 +203,6 @@ export const FooterButtonsContainer = styled(SpaceBetweenFlexRow)` background-color: ${({ theme }) => theme.color.backgroundPrimary}; `; -/** - * @deprecated to be replaced by FooterButtonsContainer when Popup layout is refactored - * to be the same as window layout - */ -export const FooterButtonsAbsoluteContainer = styled(FooterButtonsContainer)` - position: absolute; - bottom: 0; - left: 0; -`; - export const ListItemClickableContainer = styled(SpaceBetweenFlexRow)` cursor: pointer; @@ -239,7 +229,6 @@ export const TabHeaderContainer = styled(AlignedFlexRow)` export const TabFooterContainer = styled(FooterButtonsContainer)` padding: 24px 32px; - margin-top: 30px; `; export const TabTextContainer = styled.div` @@ -247,7 +236,7 @@ export const TabTextContainer = styled.div` `; export const TabPageContainer = styled.div` - padding: 24px 32px; + padding: 40px 32px 0; `; export const BreakWordContainer = styled.div` @@ -291,11 +280,17 @@ export const BorderContainer = styled.div` export const Overlay = styled.div` position: fixed; z-index: ${({ theme }) => theme.zIndex.modal}; - top: 0; - left: 0; + top: 50%; + left: 50%; + bottom: 0; + right: 0; + + transform: translate(-50%, -50%); + + overflow: auto; height: 100vh; - width: 100vw; + width: 360px; background: ${({ theme }) => hexToRGBA(theme.color.black, '0.32')}; `; @@ -340,3 +335,10 @@ export const DropdownHeader = styled(AlignedSpaceBetweenFlexRow)` background-color: ${({ theme }) => theme.color.backgroundPrimary}; `; + +export const AmountContainer = styled(SpaceBetweenFlexColumn)` + align-items: flex-end; + text-align: end; + + max-width: 120px; +`; diff --git a/src/libs/layout/error/content.tsx b/src/libs/layout/error/content.tsx index d6045848c..ca4dbb705 100644 --- a/src/libs/layout/error/content.tsx +++ b/src/libs/layout/error/content.tsx @@ -14,13 +14,13 @@ import { ErrorContent } from './types'; interface ErrorPageContentProps extends ErrorContent { // TODO: I guess will be better pass it through location state. It require extending of location state type - illustrationType: 'onboarding' | 'general'; + pageType: 'onboarding' | 'general'; } export function ErrorPageContent({ errorContentText, errorHeaderText, - illustrationType + pageType }: ErrorPageContentProps) { if (errorContentText == null || errorHeaderText == null) { throw new Error('Cannot render ErrorPage: not enough props'); @@ -29,7 +29,7 @@ export function ErrorPageContent({ return ( - {illustrationType === 'onboarding' ? ( + {pageType === 'onboarding' ? ( - {errorHeaderText} + + {errorHeaderText} + diff --git a/src/libs/layout/error/tab-page.tsx b/src/libs/layout/error/tab-page.tsx index 69e6bab90..66fce8a02 100644 --- a/src/libs/layout/error/tab-page.tsx +++ b/src/libs/layout/error/tab-page.tsx @@ -31,7 +31,7 @@ export function TabErrorPage({ )} renderFooter={() => ( diff --git a/src/libs/layout/error/window-page.tsx b/src/libs/layout/error/window-page.tsx index 1aaab4223..606c697be 100644 --- a/src/libs/layout/error/window-page.tsx +++ b/src/libs/layout/error/window-page.tsx @@ -43,7 +43,7 @@ export function WindowErrorPage({ )} renderFooter={() => ( diff --git a/src/libs/layout/header/header-submenu-bar-buy-cspr-link.tsx b/src/libs/layout/header/header-submenu-bar-buy-cspr-link.tsx new file mode 100644 index 000000000..485198b88 --- /dev/null +++ b/src/libs/layout/header/header-submenu-bar-buy-cspr-link.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import { Trans, useTranslation } from 'react-i18next'; + +import { RouterPath, useTypedNavigate } from '@popup/router'; + +import { Link, Typography } from '@libs/ui/components'; + +export const HeaderSubmenuBarBuyCSPRLink = () => { + const { t } = useTranslation(); + const navigate = useTypedNavigate(); + + return ( + navigate(RouterPath.BuyCSPR)}> + + Buy CSPR + + + ); +}; diff --git a/src/libs/layout/layout-tab.tsx b/src/libs/layout/layout-tab.tsx index c6a16c95a..52ab75265 100644 --- a/src/libs/layout/layout-tab.tsx +++ b/src/libs/layout/layout-tab.tsx @@ -3,7 +3,7 @@ import styled from 'styled-components'; import { SvgIcon } from '@libs/ui/components'; -import { CenteredFlexColumn } from './containers'; +import { CenteredFlexColumn, FlexColumn } from './containers'; const Container = styled.div` width: 100%; @@ -14,7 +14,7 @@ const Container = styled.div` const AbsoluteCenteredContainer = styled(CenteredFlexColumn)` width: 512px; - gap: 40px; + gap: 60px; position: absolute; top: 50%; @@ -24,9 +24,10 @@ const AbsoluteCenteredContainer = styled(CenteredFlexColumn)` interface ContentContainerProps { layoutContext: 'withIllustration' | 'withStepper'; + minHeight?: number | 'auto'; } -const MainContainer = styled.div` +const MainContainer = styled(FlexColumn)` background-color: ${({ theme, layoutContext }) => layoutContext === 'withIllustration' ? theme.color.backgroundPrimary @@ -36,9 +37,16 @@ const MainContainer = styled.div` height: 100%; width: 100%; + min-height: ${({ minHeight }) => + minHeight === 'auto' ? minHeight : minHeight ? `${minHeight}px` : '560px'}; overflow-y: auto; `; +const ContentContainer = styled.div` + height: 100%; + flex-grow: 1; +`; + interface LayoutProps extends ContentContainerProps { renderHeader?: () => JSX.Element; renderContent: () => JSX.Element; @@ -49,7 +57,8 @@ export function LayoutTab({ layoutContext, renderHeader, renderContent, - renderFooter + renderFooter, + minHeight }: LayoutProps) { return ( @@ -59,9 +68,9 @@ export function LayoutTab({ width={164} height={40} /> - + {renderHeader && renderHeader()} - {renderContent()} + {renderContent()} {renderFooter && renderFooter()} diff --git a/src/libs/layout/locked-router/index.tsx b/src/libs/layout/locked-router/index.tsx index 87b5df7cf..8d6ff2657 100644 --- a/src/libs/layout/locked-router/index.tsx +++ b/src/libs/layout/locked-router/index.tsx @@ -1,13 +1,8 @@ import React from 'react'; import { HashRouter, Route, Routes } from 'react-router-dom'; -import { - LayoutWindow, - PopupLayout, - ResetVaultPageContent, - UnlockVaultPageContent -} from '@libs/layout'; -import { HeaderPopup } from '@libs/layout/header/header-popup'; +import { ResetVaultPage } from '@libs/layout/reset-vault'; +import { UnlockVaultPage } from '@libs/layout/unlock-vault'; export const LockedRouterPath = { Any: '*', @@ -18,43 +13,17 @@ interface LockedRouterProps { popupLayout?: boolean; } -export function LockedRouter({ popupLayout }: LockedRouterProps) { - return ( - - - } - renderContent={() => } - /> - ) : ( - } - renderContent={() => } - /> - ) - } - /> - } - renderContent={() => } - /> - ) : ( - } - renderContent={() => } - /> - ) - } - /> - - - ); -} +export const LockedRouter = ({ popupLayout }: LockedRouterProps) => ( + + + } + /> + } + /> + + +); diff --git a/src/libs/layout/reset-vault/content.tsx b/src/libs/layout/reset-vault/content.tsx new file mode 100644 index 000000000..9f6fa1477 --- /dev/null +++ b/src/libs/layout/reset-vault/content.tsx @@ -0,0 +1,40 @@ +import React from 'react'; +import { Trans, useTranslation } from 'react-i18next'; + +import { + ContentContainer, + IllustrationContainer, + ParagraphContainer, + SpacingSize +} from '@libs/layout'; +import { SvgIcon, Typography } from '@libs/ui/components'; + +export function ResetVaultPageContent() { + const { t } = useTranslation(); + + return ( + + + + + + + Are you sure you want to reset your wallet? + + + + + + All accounts will be removed. Make sure you have securely stored + your Casper Wallet’s secret recovery phrase and any legacy secret + key files. Without them you may be unable to access the accounts. + + + + + ); +} diff --git a/src/libs/layout/reset-vault/index.tsx b/src/libs/layout/reset-vault/index.tsx index a9a986d34..96bdc8b77 100644 --- a/src/libs/layout/reset-vault/index.tsx +++ b/src/libs/layout/reset-vault/index.tsx @@ -1,24 +1,29 @@ import React, { useState } from 'react'; import { Trans, useTranslation } from 'react-i18next'; - -import { useTypedNavigate } from '@popup/router'; +import { useNavigate } from 'react-router-dom'; import { closeWindowByReloadExtension } from '@background/close-window-by-reload-extension'; import { resetVault } from '@background/redux/sagas/actions'; import { dispatchToMainStore } from '@background/redux/utils'; import { - ContentContainer, - FooterButtonsAbsoluteContainer, - IllustrationContainer, - ParagraphContainer, - SpacingSize + FooterButtonsContainer, + HeaderPopup, + LayoutWindow, + PopupLayout } from '@libs/layout'; -import { Button, Checkbox, SvgIcon, Typography } from '@libs/ui/components'; +import { Button, Checkbox } from '@libs/ui/components'; + +import { ResetVaultPageContent } from './content'; + +interface ResetVaultPageProps { + popupLayout?: boolean; +} -export function ResetVaultPageContent() { +export const ResetVaultPage = ({ popupLayout }: ResetVaultPageProps) => { const [isChecked, setIsChecked] = useState(false); - const navigate = useTypedNavigate(); + + const navigate = useNavigate(); const { t } = useTranslation(); function handleResetVault() { @@ -31,50 +36,39 @@ export function ResetVaultPageContent() { navigate(-1); } - return ( - <> - - - - - - - Are you sure you want to reset your wallet? - - - - - - All accounts will be removed. Make sure you have securely stored - your Casper Wallet’s secret recovery phrase and any legacy secret - key files. Without them you may be unable to access the accounts. - - - - - - { - setIsChecked(value); - }} - /> - - - - + const footer = ( + + { + setIsChecked(value); + }} + /> + + + ); -} + + return popupLayout ? ( + } + renderContent={() => } + renderFooter={() => footer} + /> + ) : ( + } + renderContent={() => } + renderFooter={() => footer} + /> + ); +}; diff --git a/src/libs/layout/unlock-vault/content.tsx b/src/libs/layout/unlock-vault/content.tsx new file mode 100644 index 000000000..43fd05cd4 --- /dev/null +++ b/src/libs/layout/unlock-vault/content.tsx @@ -0,0 +1,114 @@ +import React, { useState } from 'react'; +import { FieldErrors, UseFormRegister } from 'react-hook-form'; +import { Trans, useTranslation } from 'react-i18next'; +import { useSelector } from 'react-redux'; + +import { selectHasLoginRetryLockoutTime } from '@background/redux/login-retry-lockout-time/selectors'; + +import { + ContentContainer, + IllustrationContainer, + InputsContainer, + ParagraphContainer, + SpacingSize +} from '@libs/layout'; +import { + Input, + PasswordInputType, + PasswordVisibilityIcon, + SvgIcon, + Typography +} from '@libs/ui/components'; +import { UnlockWalletFormValues } from '@libs/ui/forms/unlock-wallet'; + +interface UnlockVaultPageContentProps { + register: UseFormRegister; + errors: FieldErrors; +} + +export function UnlockVaultPageContent({ + register, + errors +}: UnlockVaultPageContentProps) { + const [passwordInputType, setPasswordInputType] = + useState('password'); + + const { t } = useTranslation(); + const hasLoginRetryLockoutTime = useSelector(selectHasLoginRetryLockoutTime); + + if (hasLoginRetryLockoutTime) { + return ( + <> + + + + + + + + Please wait before the next attempt to unlock your wallet + + + + + + + You’ve reached the maximum number of unlock attempts. For + security reasons, you will need to wait a brief while before you + can attempt again. + + + + + + + You can try again in 5 mins. + + + + + + ); + } + + return ( + + + + + + + Your wallet is locked + + + + + Please enter your password to unlock. + + + + + } + {...register('password')} + /> + + + ); +} diff --git a/src/libs/layout/unlock-vault/index.tsx b/src/libs/layout/unlock-vault/index.tsx index d9fc924e4..eb9252ece 100644 --- a/src/libs/layout/unlock-vault/index.tsx +++ b/src/libs/layout/unlock-vault/index.tsx @@ -1,15 +1,25 @@ +import * as Yup from 'yup'; import { Player } from '@lottiefiles/react-lottie-player'; import React, { useState } from 'react'; +import { useForm } from 'react-hook-form'; import { Trans, useTranslation } from 'react-i18next'; import { useSelector } from 'react-redux'; import { useNavigate } from 'react-router-dom'; +import { AnyObject } from 'yup/es/types'; + +import { + ERROR_DISPLAYED_BEFORE_ATTEMPT_IS_DECREMENTED, + LOGIN_RETRY_ATTEMPTS_LIMIT +} from '@src/constants'; +import { getErrorMessageForIncorrectPassword } from '@src/utils'; import { selectKeyDerivationSaltHash, selectPasswordHash, selectPasswordSaltHash } from '@background/redux/keys/selectors'; -import { selectHasLoginRetryLockoutTime } from '@background/redux/login-retry-lockout-time/selectors'; +import { loginRetryCountIncremented } from '@background/redux/login-retry-count/actions'; +import { selectLoginRetryCount } from '@background/redux/login-retry-count/selectors'; import { unlockVault } from '@background/redux/sagas/actions'; import { UnlockVault } from '@background/redux/sagas/types'; import { dispatchToMainStore } from '@background/redux/utils'; @@ -21,44 +31,52 @@ import { useLockWalletWhenNoMoreRetries } from '@hooks/use-lock-wallet-when-no-m import unlockAnimation from '@libs/animations/unlock_animation.json'; import { AlignedFlexRow, - ContentContainer, - FooterButtonsAbsoluteContainer, - IllustrationContainer, - InputsContainer, + FooterButtonsContainer, + HeaderPopup, + LayoutWindow, LockedRouterPath, - ParagraphContainer, + PopupLayout, SpacingSize } from '@libs/layout'; -import { - Button, - Input, - PasswordInputType, - PasswordVisibilityIcon, - SvgIcon, - Typography -} from '@libs/ui/components'; -import { - UnlockWalletFormValues, - useUnlockWalletForm -} from '@libs/ui/forms/unlock-wallet'; +import { Button, Typography } from '@libs/ui/components'; +import { UnlockWalletFormValues } from '@libs/ui/forms/unlock-wallet'; + +import { UnlockVaultPageContent } from './content'; interface UnlockMessageEvent extends MessageEvent { data: UnlockVault; } -export function UnlockVaultPageContent() { - const { t } = useTranslation(); - const navigate = useNavigate(); +interface UnlockVaultPageProps { + popupLayout?: boolean; +} + +interface VerifyPasswordMessageEvent extends MessageEvent { + data: { + isPasswordCorrect: Yup.StringSchema< + string | undefined, + AnyObject, + string | undefined + >; + }; +} + +export const UnlockVaultPage = ({ popupLayout }: UnlockVaultPageProps) => { const [isLoading, setIsLoading] = useState(false); - const [passwordInputType, setPasswordInputType] = - useState('password'); + const { t } = useTranslation(); + const navigate = useNavigate(); const passwordHash = useSelector(selectPasswordHash); const passwordSaltHash = useSelector(selectPasswordSaltHash); const keyDerivationSaltHash = useSelector(selectKeyDerivationSaltHash); const vaultCipher = useSelector(selectVaultCipher); - const hasLoginRetryLockoutTime = useSelector(selectHasLoginRetryLockoutTime); + const loginRetryCount = useSelector(selectLoginRetryCount); + + const attemptsLeft = + LOGIN_RETRY_ATTEMPTS_LIMIT - + loginRetryCount - + ERROR_DISPLAYED_BEFORE_ATTEMPT_IS_DECREMENTED; if (passwordHash == null || passwordSaltHash == null) { throw Error("Password doesn't exist"); @@ -67,28 +85,56 @@ export function UnlockVaultPageContent() { const { register, handleSubmit, + formState: { errors }, resetField, - formState: { errors } - } = useUnlockWalletForm(passwordHash, passwordSaltHash); + setError + } = useForm({ + defaultValues: { + password: '' + } + }); async function handleUnlockVault({ password }: UnlockWalletFormValues) { if (isLoading) return; setIsLoading(true); + + const verifyPasswordWorker = new Worker( + new URL('@background/workers/verify-password-worker.ts', import.meta.url) + ); const unlockVaultWorker = new Worker( - new URL('@background/workers/unlockVaultWorker.ts', import.meta.url) + new URL('@background/workers/unlock-vault-worker.ts', import.meta.url) ); if (keyDerivationSaltHash == null) { throw Error("Key derivation salt doesn't exist"); } - unlockVaultWorker.postMessage({ - password, - keyDerivationSaltHash, - vaultCipher + verifyPasswordWorker.postMessage({ + passwordHash, + passwordSaltHash, + password }); + verifyPasswordWorker.onmessage = (event: VerifyPasswordMessageEvent) => { + const { isPasswordCorrect } = event.data; + const errorMessage = getErrorMessageForIncorrectPassword(attemptsLeft); + + if (!isPasswordCorrect) { + dispatchToMainStore(loginRetryCountIncremented()); + setError('password', { + message: t(errorMessage) + }); + setIsLoading(false); + } else { + unlockVaultWorker.postMessage({ + password, + keyDerivationSaltHash, + vaultCipher + }); + } + }; + unlockVaultWorker.onmessage = (event: UnlockMessageEvent) => { const { vault, @@ -145,6 +191,11 @@ export function UnlockVaultPageContent() { } }; + verifyPasswordWorker.onerror = error => { + console.error(error); + setIsLoading(false); + }; + unlockVaultWorker.onerror = error => { console.error(error); setIsLoading(false); @@ -153,111 +204,58 @@ export function UnlockVaultPageContent() { useLockWalletWhenNoMoreRetries(resetField); - if (hasLoginRetryLockoutTime) { - return ( - <> - - - + - - - + + ) : ( + + Unlock wallet + + )} + + + ); -} + + return popupLayout ? ( + } + renderContent={() => ( + + )} + renderFooter={() => footer} + /> + ) : ( + } + renderContent={() => ( + + )} + renderFooter={() => footer} + /> + ); +}; diff --git a/src/libs/services/account-activity-service/types.ts b/src/libs/services/account-activity-service/types.ts index 35953cb41..1b02c6011 100644 --- a/src/libs/services/account-activity-service/types.ts +++ b/src/libs/services/account-activity-service/types.ts @@ -242,6 +242,7 @@ export interface Metadata { export type Erc20TransferWithId = { id: string; + amount?: string; deployHash: string; callerPublicKey: string; timestamp: string; diff --git a/src/libs/ui/components/account-activity-plate/account-activity-plate.tsx b/src/libs/ui/components/account-activity-plate/account-activity-plate.tsx index f08843c23..525f9d85f 100644 --- a/src/libs/ui/components/account-activity-plate/account-activity-plate.tsx +++ b/src/libs/ui/components/account-activity-plate/account-activity-plate.tsx @@ -106,22 +106,29 @@ export const AccountActivityPlate = forwardRef( : callerPublicKey; try { - const parsedAmount = - ((typeof args?.amount?.parsed === 'string' || - typeof args?.amount?.parsed === 'number') && - args?.amount?.parsed) || - '-'; - - if (parsedAmount !== '-') { - const stringAmount = - typeof parsedAmount === 'number' - ? parsedAmount.toString() - : parsedAmount; - + if (transactionInfo.amount) { amount = Number.isInteger(decimals) && decimals !== undefined - ? divideErc20Balance(stringAmount, decimals) - : motesToCSPR(stringAmount); + ? divideErc20Balance(transactionInfo.amount, decimals) + : motesToCSPR(transactionInfo.amount); + } else { + const parsedAmount = + ((typeof args?.amount?.parsed === 'string' || + typeof args?.amount?.parsed === 'number') && + args?.amount?.parsed) || + '-'; + + if (parsedAmount !== '-') { + const stringAmount = + typeof parsedAmount === 'number' + ? parsedAmount.toString() + : parsedAmount; + + amount = + Number.isInteger(decimals) && decimals !== undefined + ? divideErc20Balance(stringAmount, decimals) + : motesToCSPR(stringAmount); + } } } catch (error) { console.error(error); diff --git a/src/libs/ui/components/account-popover/account-popover.tsx b/src/libs/ui/components/account-popover/account-popover.tsx index 4cd600ef4..9eaaa8fc8 100644 --- a/src/libs/ui/components/account-popover/account-popover.tsx +++ b/src/libs/ui/components/account-popover/account-popover.tsx @@ -10,7 +10,7 @@ import { RouterPath, useTypedNavigate } from '@popup/router'; import { selectActiveOrigin } from '@background/redux/active-origin/selectors'; import { selectApiConfigBasedOnActiveNetwork } from '@background/redux/settings/selectors'; import { dispatchToMainStore } from '@background/redux/utils'; -import { hideAccountFromListChange } from '@background/redux/vault/actions'; +import { hideAccountFromListChanged } from '@background/redux/vault/actions'; import { selectConnectedAccountNamesWithActiveOrigin, selectIsAnyAccountConnectedWithActiveOrigin @@ -133,7 +133,7 @@ export const AccountActionsMenuPopover = ({ variant="contentAction" onClick={() => { dispatchToMainStore( - hideAccountFromListChange({ accountName: account.name }) + hideAccountFromListChanged({ accountName: account.name }) ); }} > diff --git a/src/libs/ui/components/active-account-plate/active-account-plate.tsx b/src/libs/ui/components/active-account-plate/active-account-plate.tsx index f638d7ae4..bb43999ed 100644 --- a/src/libs/ui/components/active-account-plate/active-account-plate.tsx +++ b/src/libs/ui/components/active-account-plate/active-account-plate.tsx @@ -22,7 +22,7 @@ import { Typography } from '@libs/ui/components'; -export const AmountContainer = styled(SpaceBetweenFlexColumn)` +const AmountContainer = styled(SpaceBetweenFlexColumn)` align-items: flex-end; max-width: 120px; @@ -42,12 +42,14 @@ interface ActiveAccountPlateProps { label: string; balance: string | null; symbol: string | null; + top?: SpacingSize; } export const ActiveAccountPlate = ({ label, symbol, - balance + balance, + top }: ActiveAccountPlateProps) => { const { t } = useTranslation(); @@ -59,7 +61,7 @@ export const ActiveAccountPlate = ({ return ( <> - + {label} diff --git a/src/libs/ui/components/avatar/avatar.tsx b/src/libs/ui/components/avatar/avatar.tsx index c4d851db6..93b616cbc 100644 --- a/src/libs/ui/components/avatar/avatar.tsx +++ b/src/libs/ui/components/avatar/avatar.tsx @@ -69,7 +69,8 @@ export const Avatar = ({ return ( ` - padding: 20px 16px; + padding: 12px 16px; cursor: ${({ disabled }) => (disabled ? 'not-allowed' : 'pointer')}; `; @@ -59,15 +60,14 @@ const AmountContainer = styled(FlexColumn)` `; interface ListProps { - ledgerAccountsWithBalance: ILedgerAccountListItem[]; + accountsWithBalance: (ILedgerAccountListItem | AccountListRows)[]; setIsButtonDisabled: React.Dispatch>; - selectedAccounts: ILedgerAccountListItem[]; - setSelectedAccounts: React.Dispatch< - React.SetStateAction - >; + selectedAccounts: (ILedgerAccountListItem | AccountListRows)[]; + setSelectedAccounts: React.Dispatch>; maxItemsToRender: number; onLoadMore: () => void; isLoadingMore: boolean; + namePrefix: string; } type FormFields = FieldValues & { @@ -75,14 +75,15 @@ type FormFields = FieldValues & { checkbox: boolean[]; }; -export const LedgerAccountsList = ({ - ledgerAccountsWithBalance, +export const DynamicAccountsListWithSelect = ({ + accountsWithBalance, setIsButtonDisabled, selectedAccounts, setSelectedAccounts, maxItemsToRender, onLoadMore, - isLoadingMore + isLoadingMore, + namePrefix }: ListProps) => { const [accountNames, setAccountNames] = useState<{ name: string }[]>([]); const [checkboxes, setCheckboxes] = useState([]); @@ -113,14 +114,10 @@ export const LedgerAccountsList = ({ }); useEffect(() => { - for ( - let i = inputsFields.length; - i < ledgerAccountsWithBalance.length; - i++ - ) { - append({ name: `Ledger account ${i + 1}` }); + for (let i = inputsFields.length; i < accountsWithBalance.length; i++) { + append({ name: `${namePrefix} ${i + 1}` }); } - }, [append, inputsFields.length, ledgerAccountsWithBalance]); + }, [append, inputsFields.length, accountsWithBalance, namePrefix]); useEffect(() => { setAccountNames(getValues('accountNames')); @@ -148,9 +145,10 @@ export const LedgerAccountsList = ({ return ( - rows={ledgerAccountsWithBalance} + rows={accountsWithBalance} contentTop={SpacingSize.XL} maxItemsToRender={maxItemsToRender} + maxHeight={290} renderRow={(account, index) => { const inputFieldName = `accountNames.${index}.name`; const checkBoxFieldName = `checkbox.${index}`; @@ -185,6 +183,7 @@ export const LedgerAccountsList = ({ control={control} render={({ field: checkboxControllerField }) => ( { if (isAlreadyConnected) return; diff --git a/src/libs/ui/components/erc20-token-activity-list/erc20-token-activity-list.tsx b/src/libs/ui/components/erc20-token-activity-list/erc20-token-activity-list.tsx index 922dc5d56..f0d6ca1e4 100644 --- a/src/libs/ui/components/erc20-token-activity-list/erc20-token-activity-list.tsx +++ b/src/libs/ui/components/erc20-token-activity-list/erc20-token-activity-list.tsx @@ -57,6 +57,7 @@ export const Erc20TokenActivityList = () => { tokenActivity?.tokenActivityList?.map((transaction, index) => { return { id: String(index), + amount: transaction.amount, deployHash: transaction.deploy_hash, callerPublicKey: transaction.deploy?.caller_public_key || '-', timestamp: transaction.deploy?.timestamp || '-', diff --git a/src/libs/ui/components/index.ts b/src/libs/ui/components/index.ts index eaf008ce1..6a133fd89 100644 --- a/src/libs/ui/components/index.ts +++ b/src/libs/ui/components/index.ts @@ -30,6 +30,7 @@ export * from './tabs/tabs'; export * from './token-plate/token-plate'; export * from './deploy-status/deploy-status'; export * from './modal/modal'; +export * from './modal/modal-switcher'; export * from './active-account-plate/active-account-plate'; export * from './recipient-plate/recipient-plate'; export * from './account-casper-activity-plate/account-casper-activity-plate'; @@ -52,9 +53,12 @@ export * from './theme-switcher/theme-switcher'; export * from './identicon/identicon'; export * from './spinner/spinner'; export * from './tips/tips'; +export * from './recipient-tabs/recipient-tabs'; +export * from './transaction-fee-plate/transaction-fee-plate'; export * from './review-with-ledger/review-with-ledger'; export * from './no-connected-ledger/no-connected-ledger'; export * from './ledger-footer/ledger-footer'; export * from './ledger-error-view/ledger-error-view'; export * from './ledger-event-view/ledger-event-view'; export * from './ledger-connection-view/ledger-connection-view'; +export * from './dynamic-accounts-list-with-select/dynamic-accounts-list-with-select'; diff --git a/src/libs/ui/components/input/input.tsx b/src/libs/ui/components/input/input.tsx index 9d4e32ead..40cf71f39 100644 --- a/src/libs/ui/components/input/input.tsx +++ b/src/libs/ui/components/input/input.tsx @@ -6,14 +6,6 @@ import { BaseProps } from '@libs/ui/types'; type Ref = HTMLInputElement; -const getThemeColorByError = (error?: boolean) => { - if (error == null || !error) { - return 'contentDisabled'; - } - - return 'fillCritical'; -}; - const InputContainer = styled('div')( ({ theme, @@ -45,7 +37,9 @@ const InputContainer = styled('div')( path: { fill: oneColoredIcons ? theme.color.contentDisabled - : theme.color[getThemeColorByError(error)] + : error + ? theme.color.fillCritical + : '' }, ...((disabled || readOnly) && { diff --git a/src/apps/popup/pages/buy-cspr/components/switcher.tsx b/src/libs/ui/components/modal/modal-switcher.tsx similarity index 75% rename from src/apps/popup/pages/buy-cspr/components/switcher.tsx rename to src/libs/ui/components/modal/modal-switcher.tsx index aafe4e0ba..30038b4a6 100644 --- a/src/apps/popup/pages/buy-cspr/components/switcher.tsx +++ b/src/libs/ui/components/modal/modal-switcher.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import { FieldValues, UseFormRegister } from 'react-hook-form'; import { Trans, useTranslation } from 'react-i18next'; import styled from 'styled-components'; @@ -7,20 +6,23 @@ import { AlignedFlexRow, FlexColumn, FooterButtonsContainer, - InputsContainer, ParagraphContainer, SpacingSize } from '@libs/layout'; -import { Button, Input, SvgIcon, Typography } from '@libs/ui/components'; +import { Button, Typography } from '@libs/ui/components'; const Container = styled(FlexColumn)` background-color: ${({ theme }) => theme.color.backgroundSecondary}; border-top-right-radius: ${({ theme }) => theme.borderRadius.sixteen}px; border-top-left-radius: ${({ theme }) => theme.borderRadius.sixteen}px; + + height: 528px; `; const ContentContainer = styled.div` padding: 0 16px; + + flex-grow: 1; `; const HeaderContainer = styled(AlignedFlexRow)` @@ -36,19 +38,15 @@ const CancelButton = styled(Typography)` `; interface SwitcherProps { - label: 'Country' | 'Currency'; + label: string; closeSwitcher: (e: React.MouseEvent) => void; children: React.ReactNode; - register: UseFormRegister; - searchName: 'countryNameSearch' | 'currencySearch'; } -export const Switcher = ({ +export const ModalSwitcher = ({ label, closeSwitcher, - children, - register, - searchName + children }: SwitcherProps) => { const { t } = useTranslation(); @@ -71,14 +69,6 @@ export const Switcher = ({ - - } - placeholder={t('Search')} - {...register(searchName)} - /> - - {children} diff --git a/src/libs/ui/components/modal/modal.tsx b/src/libs/ui/components/modal/modal.tsx index 73f08b69f..eb536cb92 100644 --- a/src/libs/ui/components/modal/modal.tsx +++ b/src/libs/ui/components/modal/modal.tsx @@ -33,7 +33,7 @@ const slideOutToBottom = keyframes` const ModalContainer = styled.div<{ placement: 'top' | 'bottom' | 'fullBottom'; }>(({ theme, placement }) => ({ - position: 'fixed', + position: 'absolute', margin: '0 16px', diff --git a/src/libs/ui/components/recipient-tabs/components/contacts-list.tsx b/src/libs/ui/components/recipient-tabs/components/contacts-list.tsx new file mode 100644 index 000000000..6ea40ade6 --- /dev/null +++ b/src/libs/ui/components/recipient-tabs/components/contacts-list.tsx @@ -0,0 +1,60 @@ +import React, { useEffect, useState } from 'react'; +import { useSelector } from 'react-redux'; + +import { selectAllContacts } from '@background/redux/contacts/selectors'; +import { ContactWithId } from '@background/redux/contacts/types'; +import { selectVaultActiveAccount } from '@background/redux/vault/selectors'; + +import { SpacingSize, TileContainer } from '@libs/layout'; +import { List, RecipientPlate, Tile, Typography } from '@libs/ui/components'; + +interface ContactsListProps { + handleSelectRecipient: (publicKey: string, name: string) => void; +} + +export const ContactsList = ({ handleSelectRecipient }: ContactsListProps) => { + const [contactsWithId, setContactsWithId] = useState([]); + + const contacts = useSelector(selectAllContacts); + const activeAccount = useSelector(selectVaultActiveAccount); + + useEffect(() => { + const contactsWithId = contacts + .map(contact => ({ + ...contact, + id: contact.name + })) + .filter(account => account.publicKey !== activeAccount?.publicKey); + + setContactsWithId(contactsWithId); + }, [contacts, activeAccount]); + + if (contactsWithId.length === 0) { + return ( + + + + No contacts found + + + + ); + } + + return ( + ( + { + handleSelectRecipient(contact.publicKey, contact.name); + }} + /> + )} + marginLeftForItemSeparatorLine={56} + /> + ); +}; diff --git a/src/libs/ui/components/recipient-tabs/components/my-accounts-list.tsx b/src/libs/ui/components/recipient-tabs/components/my-accounts-list.tsx new file mode 100644 index 000000000..87a8f4404 --- /dev/null +++ b/src/libs/ui/components/recipient-tabs/components/my-accounts-list.tsx @@ -0,0 +1,52 @@ +import React, { useEffect, useState } from 'react'; +import { useSelector } from 'react-redux'; + +import { + selectVaultAccountsWithBalances, + selectVaultActiveAccount +} from '@background/redux/vault/selectors'; + +import { SpacingSize } from '@libs/layout'; +import { AccountListRows } from '@libs/types/account'; +import { List, RecipientPlate } from '@libs/ui/components'; + +interface MyAccountsListProps { + handleSelectRecipient: (publicKey: string, name: string) => void; +} + +export const MyAccountsList = ({ + handleSelectRecipient +}: MyAccountsListProps) => { + const [accountsWithIds, setAccountsWithIds] = useState([]); + + const accounts = useSelector(selectVaultAccountsWithBalances); + const activeAccount = useSelector(selectVaultActiveAccount); + + useEffect(() => { + const accountsWithIds = accounts + .map(account => ({ + ...account, + id: account.name + })) + .filter(account => account.publicKey !== activeAccount?.publicKey); + + setAccountsWithIds(accountsWithIds); + }, [accounts, setAccountsWithIds, activeAccount]); + + return ( + ( + { + handleSelectRecipient(account.publicKey, account.name); + }} + /> + )} + marginLeftForItemSeparatorLine={56} + /> + ); +}; diff --git a/src/libs/ui/components/recipient-tabs/components/recent-list.tsx b/src/libs/ui/components/recipient-tabs/components/recent-list.tsx new file mode 100644 index 000000000..9c5ff5059 --- /dev/null +++ b/src/libs/ui/components/recipient-tabs/components/recent-list.tsx @@ -0,0 +1,82 @@ +import React, { useEffect, useState } from 'react'; +import { useSelector } from 'react-redux'; + +import { selectAllContacts } from '@background/redux/contacts/selectors'; +import { selectRecentRecipientPublicKeys } from '@background/redux/recent-recipient-public-keys/selectors'; +import { selectVaultActiveAccount } from '@background/redux/vault/selectors'; + +import { SpacingSize, TileContainer } from '@libs/layout'; +import { List, RecipientPlate, Tile, Typography } from '@libs/ui/components'; + +interface MyAccountsListProps { + handleSelectRecipient: (publicKey: string, name: string) => void; +} + +interface RecentListState { + publicKey: string; + id: string; + name: string; +} + +export const RecentList = ({ handleSelectRecipient }: MyAccountsListProps) => { + const [accountsWithIds, setAccountsWithIds] = useState([]); + + const recentRecipientPublicKeys = useSelector( + selectRecentRecipientPublicKeys + ); + const contacts = useSelector(selectAllContacts); + const activeAccount = useSelector(selectVaultActiveAccount); + + useEffect(() => { + const recentRecipient = recentRecipientPublicKeys + .map(publicKey => { + const contact = contacts.find( + contact => contact.publicKey === publicKey + ); + if (contact) { + return { + name: contact.name, + publicKey: publicKey, + id: publicKey + }; + } + return { + name: '', + publicKey: publicKey, + id: publicKey + }; + }) + .filter(account => account.publicKey !== activeAccount?.publicKey); + + setAccountsWithIds(recentRecipient); + }, [contacts, recentRecipientPublicKeys, setAccountsWithIds, activeAccount]); + + if (!accountsWithIds.length) { + return ( + + + + No recent recipients were found. + + + + ); + } + + return ( + ( + { + handleSelectRecipient(recent.publicKey, recent.name); + }} + /> + )} + marginLeftForItemSeparatorLine={56} + /> + ); +}; diff --git a/src/libs/ui/components/recipient-tabs/recipient-tabs.tsx b/src/libs/ui/components/recipient-tabs/recipient-tabs.tsx new file mode 100644 index 000000000..51a10d932 --- /dev/null +++ b/src/libs/ui/components/recipient-tabs/recipient-tabs.tsx @@ -0,0 +1,157 @@ +import React, { useEffect, useState } from 'react'; +import { UseFormReturn, useWatch } from 'react-hook-form'; +import { useTranslation } from 'react-i18next'; + +import { useClickAway } from '@hooks/use-click-away'; + +import { SpacingSize, VerticalSpaceContainer } from '@libs/layout'; +import { Input, RecipientPlate, SvgIcon, Tab, Tabs } from '@libs/ui/components'; +import { TransferRecipientFormValues } from '@libs/ui/forms/transfer'; +import { TransferNftRecipientFormValues } from '@libs/ui/forms/transfer-nft'; + +import { ContactsList } from './components/contacts-list'; +import { MyAccountsList } from './components/my-accounts-list'; +import { RecentList } from './components/recent-list'; +import { RecipientTabName } from './utils'; + +interface RecipientTabsProps { + recipientForm: UseFormReturn< + TransferRecipientFormValues | TransferNftRecipientFormValues + >; + setRecipientPublicKey?: (value: React.SetStateAction) => void; + setRecipientName: React.Dispatch>; + recipientName: string; +} + +export const RecipientTabs = ({ + recipientForm, + setRecipientPublicKey, + setRecipientName, + recipientName +}: RecipientTabsProps) => { + const [showRecipientPlate, setShowRecipientPlate] = useState(false); + + const { t } = useTranslation(); + + const { register, trigger, control, formState, setValue } = recipientForm; + const { errors } = formState; + const { onChange } = register('recipientPublicKey'); + + const inputValue = useWatch({ + control: control, + name: 'recipientPublicKey' + }); + + const { ref: clickAwayRef } = useClickAway({ + callback: () => { + if (formState.isValid) { + setShowRecipientPlate(true); + } + } + }); + + useEffect(() => { + if (formState.isValid) { + setShowRecipientPlate(true); + } + // This should trigger only once + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const handleIconClick = () => { + navigator.clipboard + .readText() + .then(clipboardText => { + setValue('recipientPublicKey', clipboardText); + if (setRecipientPublicKey) { + setRecipientPublicKey(clipboardText); + } + + trigger('recipientPublicKey').then(isValid => { + if (isValid) { + setShowRecipientPlate(true); + } + }); + }) + .catch(err => { + console.error('Could not read clipboard: ', err); + }); + }; + + const handleSelectRecipient = (publicKey: string, name: string) => { + if (setRecipientPublicKey) { + setRecipientPublicKey(publicKey); + } + setValue('recipientPublicKey', publicKey); + + setShowRecipientPlate(true); + setRecipientName(name); + + trigger('recipientPublicKey'); + }; + + return showRecipientPlate ? ( + + { + setShowRecipientPlate(false); + + trigger('recipientPublicKey'); + }} + /> + + ) : ( + { + setShowRecipientPlate(false); + }} + > + } + suffixIcon={ + !!errors?.recipientPublicKey ? null : ( + + ) + } + placeholder={t('Public key')} + {...register('recipientPublicKey')} + onChange={e => { + onChange(e); + + if (setRecipientPublicKey) { + setRecipientPublicKey(e.target.value); + } + }} + error={!!errors?.recipientPublicKey} + validationText={errors?.recipientPublicKey?.message} + autoComplete="off" + /> + + + + + + + + + + + + + + + ); +}; diff --git a/src/libs/ui/components/recipient-tabs/utils.ts b/src/libs/ui/components/recipient-tabs/utils.ts new file mode 100644 index 000000000..77f9bdbe3 --- /dev/null +++ b/src/libs/ui/components/recipient-tabs/utils.ts @@ -0,0 +1,5 @@ +export enum RecipientTabName { + Recent = 'Recent', + MyAccounts = 'My accounts', + Contacts = 'Contacts' +} diff --git a/src/libs/ui/components/tabs/tabs.tsx b/src/libs/ui/components/tabs/tabs.tsx index fd673d01c..fa4cbc589 100644 --- a/src/libs/ui/components/tabs/tabs.tsx +++ b/src/libs/ui/components/tabs/tabs.tsx @@ -63,7 +63,7 @@ export function Tabs({ children, preferActiveTabId }: TabsProps) { return ( <> - + {children.map((tab, index) => { const { tabName } = tab.props; diff --git a/src/libs/ui/components/token-plate/token-plate.tsx b/src/libs/ui/components/token-plate/token-plate.tsx index 66fdc2c5f..636f35904 100644 --- a/src/libs/ui/components/token-plate/token-plate.tsx +++ b/src/libs/ui/components/token-plate/token-plate.tsx @@ -46,6 +46,7 @@ export const TokenPlate = ({ }: TokenPlateProps) => { const tokenIconFormat = token?.icon?.split('.').pop(); const isTokenIconJPG = tokenIconFormat === 'jpg'; + const isTokenIconPNG = tokenIconFormat === 'png'; return ( - {isTokenIconJPG ? ( + {isTokenIconJPG || isTokenIconPNG ? ( { + const { t } = useTranslation(); + + const currencyRate = useSelector(selectAccountCurrencyRate); + + return ( + + + + Transaction fee + + + + {formatNumber(paymentAmount, { + precision: { max: 5 } + })}{' '} + CSPR + + + {formatFiatAmount( + motesToCSPR(TRANSFER_COST_MOTES) || '0', + currencyRate, + 3 + )} + + + + + ); +}; diff --git a/src/libs/ui/components/typography/typography.tsx b/src/libs/ui/components/typography/typography.tsx index c1f03e74f..a2d2bb4fe 100644 --- a/src/libs/ui/components/typography/typography.tsx +++ b/src/libs/ui/components/typography/typography.tsx @@ -9,6 +9,7 @@ type Ref = HTMLSpanElement | HTMLHeadingElement; export type TypographyType = | 'header' + | 'headerBig' | 'body' | 'bodySemiBold' | 'bodyHash' @@ -220,21 +221,37 @@ const StyledTypography = styled('span').withConfig({ const StyledHeader = styled('h1').withConfig({ shouldForwardProp: (prop, defaultValidatorFn) => !['loading'].includes(prop) && defaultValidatorFn(prop) -})(({ theme, ...props }) => { +})(({ theme, type, ...props }) => { const body = getBodyStyles(theme, props); - return { - ...body, - fontWeight: theme.typography.fontWeight.bold, - fontSize: '2.4rem', - lineHeight: '2.8rem' - }; + + switch (type) { + case 'header': { + return { + ...body, + fontWeight: theme.typography.fontWeight.bold, + fontSize: '2.4rem', + lineHeight: '2.8rem' + }; + } + case 'headerBig': { + return { + ...body, + fontWeight: theme.typography.fontWeight.bold, + fontSize: '2.8rem', + lineHeight: '3.6rem' + }; + } + } }); export const Typography = forwardRef(function Typography( { dataTestId, ...props }, ref ) { - const Component = props.type !== 'header' ? StyledTypography : StyledHeader; + const Component = + props.type !== 'header' && props.type !== 'headerBig' + ? StyledTypography + : StyledHeader; if (props.loading) { return ( diff --git a/src/libs/ui/forms/create-account.ts b/src/libs/ui/forms/create-account.ts index 63826a7fc..382166e93 100644 --- a/src/libs/ui/forms/create-account.ts +++ b/src/libs/ui/forms/create-account.ts @@ -2,6 +2,9 @@ import * as Yup from 'yup'; import { yupResolver } from '@hookform/resolvers/yup/dist/yup'; import { UseFormProps, useForm } from 'react-hook-form'; +import { SecretPhrase, deriveKeyPair } from '@libs/crypto'; +import { AccountWithBalance } from '@libs/types/account'; + import { useAccountNameRule } from './form-validation-rules'; export type CreateAccountFormValues = { @@ -30,12 +33,28 @@ export function useCreateAccountForm( } export function getDefaultName( - derivedAccountsCount: number, - existingAccountNames: string[] + existingAccountNames: string[], + derivedAccounts: AccountWithBalance[], + secretPhrase: SecretPhrase | null ) { const accountString = 'Account'; - let sequenceNumber = derivedAccountsCount + 1; + let isAccountAlreadyAdded = true; + let i = 0; + + while (isAccountAlreadyAdded) { + const keyPair = deriveKeyPair(secretPhrase, i); + + if ( + !derivedAccounts.some(account => account.publicKey === keyPair.publicKey) + ) { + isAccountAlreadyAdded = false; + break; + } + i++; + } + + let sequenceNumber = i + 1; let defaultName = `${accountString} ${sequenceNumber}`; while (existingAccountNames.includes(defaultName)) { diff --git a/src/libs/ui/forms/form-validation-rules.ts b/src/libs/ui/forms/form-validation-rules.ts index 0e5f3a20e..75d9e4a61 100644 --- a/src/libs/ui/forms/form-validation-rules.ts +++ b/src/libs/ui/forms/form-validation-rules.ts @@ -6,13 +6,19 @@ import { useSelector } from 'react-redux'; import { AuctionManagerEntryPoint, DELEGATION_MIN_AMOUNT_MOTES, + ERROR_DISPLAYED_BEFORE_ATTEMPT_IS_DECREMENTED, LOGIN_RETRY_ATTEMPTS_LIMIT, MAX_DELEGATORS, STAKE_COST_MOTES, TRANSFER_COST_MOTES, TRANSFER_MIN_AMOUNT_MOTES } from '@src/constants'; -import { isValidPublicKey, isValidSecretKeyHash, isValidU64 } from '@src/utils'; +import { + getErrorMessageForIncorrectPassword, + isValidPublicKey, + isValidSecretKeyHash, + isValidU64 +} from '@src/utils'; import { loginRetryCountIncremented } from '@background/redux/login-retry-count/actions'; import { selectLoginRetryCount } from '@background/redux/login-retry-count/selectors'; @@ -23,8 +29,6 @@ import { CSPRtoMotes, motesToCSPR } from '@libs/ui/utils/formatters'; export const minPasswordLength = 16; -const ERROR_DISPLAYED_BEFORE_ATTEMPT_IS_DECREMENTED = 1; - export function useCreatePasswordRule() { const { t } = useTranslation(); const passwordAmountCharactersMessage = t( @@ -38,7 +42,6 @@ export function useVerifyPasswordAgainstHashRule( passwordHash: string, passwordSaltHash: string ) { - const { t } = useTranslation(); const loginRetryCount = useSelector(selectLoginRetryCount); const attemptsLeft = @@ -46,14 +49,7 @@ export function useVerifyPasswordAgainstHashRule( loginRetryCount - ERROR_DISPLAYED_BEFORE_ATTEMPT_IS_DECREMENTED; - const errorMessage = - attemptsLeft === 1 - ? t( - 'Password is incorrect. You’ve got last attempt, after that you’ll have to wait for 5 mins' - ) - : t( - `Password is incorrect. You’ve got ${attemptsLeft} attempts, after that you’ll have to wait for 5 mins` - ); + const errorMessage = getErrorMessageForIncorrectPassword(attemptsLeft); return Yup.string().test('authenticate', errorMessage, async password => { const result = await verifyPasswordAgainstHash( @@ -189,10 +185,25 @@ export const useCSPRTransferAmountRule = (amountMotes: string | null) => { message: t( 'Your account balance is not high enough. Enter a smaller amount.' ) + }) + .test({ + name: 'decimalPartLength', + test: amountInputValue => { + if (amountInputValue) { + const decimalPart = amountInputValue.split('.')[1]; + return decimalPart == null || decimalPart.length <= 9; + } + + return false; + }, + message: t('No more than 9 decimals') }); }; -export const useErc20AmountRule = (amount: string | null) => { +export const useErc20AmountRule = ( + amount: string | null, + decimals: number | undefined +) => { const { t } = useTranslation(); const maxAmount: string = amount == null ? '0' : Big(amount).toFixed(); @@ -233,6 +244,22 @@ export const useErc20AmountRule = (amount: string | null) => { message: t( 'Your account balance is not high enough. Enter a smaller amount.' ) + }) + .test({ + name: 'decimalPartLength', + test: amountInputValue => { + if (amountInputValue) { + const decimalPart = amountInputValue.split('.')[1]; + if (decimals || decimals === 0) { + return decimalPart == null || decimalPart.length <= decimals; + } + + return true; + } + + return false; + }, + message: t(`No more than ${decimals} decimals`) }); }; @@ -403,8 +430,9 @@ export const useCSPRStakeAmountRule = ( }; export const useValidatorPublicKeyRule = ( - stakesType: AuctionManagerEntryPoint, - delegatorsNumber?: number + stakeType: AuctionManagerEntryPoint, + delegatorsNumber?: number, + hasDelegationToSelectedValidator?: boolean ) => { const { t } = useTranslation(); @@ -419,16 +447,16 @@ export const useValidatorPublicKeyRule = ( name: 'maxDelegators', test: () => { if ( - stakesType === AuctionManagerEntryPoint.undelegate || - stakesType === AuctionManagerEntryPoint.redelegate + stakeType === AuctionManagerEntryPoint.undelegate || + stakeType === AuctionManagerEntryPoint.redelegate ) { return true; } - if (delegatorsNumber) { + if (delegatorsNumber && !hasDelegationToSelectedValidator) { return delegatorsNumber < MAX_DELEGATORS; } - return false; + return !!hasDelegationToSelectedValidator; }, message: t( 'This validator has reached the network limit for total delegators and therefore cannot be delegated to by new accounts. Please select another validator with fewer than 1200 total delegators' @@ -436,7 +464,10 @@ export const useValidatorPublicKeyRule = ( }); }; -export const useNewValidatorPublicKeyRule = (delegatorsNumber?: number) => { +export const useNewValidatorPublicKeyRule = ( + delegatorsNumber?: number, + hasDelegationToSelectedNewValidator?: boolean +) => { const { t } = useTranslation(); return Yup.string() @@ -449,11 +480,11 @@ export const useNewValidatorPublicKeyRule = (delegatorsNumber?: number) => { .test({ name: 'maxDelegators', test: () => { - if (delegatorsNumber) { + if (delegatorsNumber && !hasDelegationToSelectedNewValidator) { return delegatorsNumber < MAX_DELEGATORS; } - return false; + return !!hasDelegationToSelectedNewValidator; }, message: t( 'This validator has reached the network limit for total delegators and therefore cannot be delegated to by new accounts. Please select another validator with fewer than 1200 total delegators' diff --git a/src/libs/ui/forms/stakes-form.ts b/src/libs/ui/forms/stakes-form.ts index 4ffe878ce..4b40c5660 100644 --- a/src/libs/ui/forms/stakes-form.ts +++ b/src/libs/ui/forms/stakes-form.ts @@ -24,13 +24,19 @@ export type StakeAmountFormValues = { export const useStakesForm = ( amountMotes: string | null, - stakesType: AuctionManagerEntryPoint, + stakeType: AuctionManagerEntryPoint, stakeAmountMotes: string, delegatorsNumber?: number, - delegatorsNumberForNewValidator?: number + delegatorsNumberForNewValidator?: number, + hasDelegationToSelectedValidator?: boolean, + hasDelegationToSelectedNewValidator?: boolean ) => { const validatorFormSchema = Yup.object().shape({ - validatorPublicKey: useValidatorPublicKeyRule(stakesType, delegatorsNumber) + validatorPublicKey: useValidatorPublicKeyRule( + stakeType, + delegatorsNumber, + hasDelegationToSelectedValidator + ) }); const validatorFormOptions: UseFormProps = { @@ -41,7 +47,8 @@ export const useStakesForm = ( const newValidatorFromSchema = Yup.object().shape({ newValidatorPublicKey: useNewValidatorPublicKeyRule( - delegatorsNumberForNewValidator + delegatorsNumberForNewValidator, + hasDelegationToSelectedNewValidator ) }); @@ -52,7 +59,7 @@ export const useStakesForm = ( }; const amountFormSchema = Yup.object().shape({ - amount: useCSPRStakeAmountRule(amountMotes, stakesType, stakeAmountMotes) + amount: useCSPRStakeAmountRule(amountMotes, stakeType, stakeAmountMotes) }); const amountFormOptions: UseFormProps = { diff --git a/src/libs/ui/forms/transfer.ts b/src/libs/ui/forms/transfer.ts index db4e9e604..3724d6145 100644 --- a/src/libs/ui/forms/transfer.ts +++ b/src/libs/ui/forms/transfer.ts @@ -23,12 +23,7 @@ export type TransferAmountFormValues = { transferIdMemo: string; }; -export function useTransferForm( - erc20Balance: string | null, - isErc20: boolean, - amountMotes: string | null, - paymentAmount: string -) { +export const useTransferRecipientForm = () => { const recipientFormSchema = Yup.object().shape({ recipientPublicKey: useRecipientPublicKeyRule() }); @@ -40,8 +35,18 @@ export function useTransferForm( delayError: 500 }; + return useForm(recipientFormOptions); +}; + +export const useTransferAmountForm = ( + erc20Balance: string | null, + isErc20: boolean, + amountMotes: string | null, + paymentAmount: string, + decimals: number | undefined +) => { const erc20AmountFormSchema = Yup.object().shape({ - amount: useErc20AmountRule(erc20Balance), + amount: useErc20AmountRule(erc20Balance, decimals), paymentAmount: usePaymentAmountRule(amountMotes), transferIdMemo: useTransferIdMemoRule() }); @@ -68,8 +73,5 @@ export function useTransferForm( } }; - return { - recipientForm: useForm(recipientFormOptions), - amountForm: useForm(amountFormOptions) - }; -} + return useForm(amountFormOptions); +}; diff --git a/src/manifest.v3.json b/src/manifest.v3.json index b2b5122f5..01519554c 100644 --- a/src/manifest.v3.json +++ b/src/manifest.v3.json @@ -11,7 +11,9 @@ "management", "storage", "tabs", - "declarativeNetRequest" + "declarativeNetRequest", + "clipboardRead", + "clipboardWrite" ], "declarative_net_request": { "rule_resources": [ diff --git a/src/utils.ts b/src/utils.ts index 63a7cf3b3..a1b0afefb 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -399,3 +399,8 @@ export const fetchAndDispatchExtendedDeployInfo = (deployHash: string) => { // Note: this timeout is needed because the deploy is not immediately visible in the explorer }, 2000); }; + +export const getErrorMessageForIncorrectPassword = (attemptsLeft: number) => + attemptsLeft === 1 + ? 'Password is incorrect. You’ve got last attempt, after that you’ll have to wait for 5 mins' + : `Password is incorrect. You’ve got ${attemptsLeft} attempts, after that you’ll have to wait for 5 mins`; diff --git a/xcode-project/Casper Wallet/Casper Wallet.xcodeproj/project.pbxproj b/xcode-project/Casper Wallet/Casper Wallet.xcodeproj/project.pbxproj index 4f85ea8b2..67ac52993 100644 --- a/xcode-project/Casper Wallet/Casper Wallet.xcodeproj/project.pbxproj +++ b/xcode-project/Casper Wallet/Casper Wallet.xcodeproj/project.pbxproj @@ -20,7 +20,6 @@ 2439D7C029F0757C00F07C90 /* connectToApp.bundle.js in Resources */ = {isa = PBXBuildFile; fileRef = 2439D7AA29F0757C00F07C90 /* connectToApp.bundle.js */; }; 2439D7C129F0757C00F07C90 /* logo16.png in Resources */ = {isa = PBXBuildFile; fileRef = 2439D7AB29F0757C00F07C90 /* logo16.png */; }; 2439D7C229F0757C00F07C90 /* connect-to-app.html in Resources */ = {isa = PBXBuildFile; fileRef = 2439D7AC29F0757C00F07C90 /* connect-to-app.html */; }; - 2439D7C329F0757C00F07C90 /* 909.bundle.js in Resources */ = {isa = PBXBuildFile; fileRef = 2439D7AD29F0757C00F07C90 /* 909.bundle.js */; }; 2439D7C429F0757C00F07C90 /* locales in Resources */ = {isa = PBXBuildFile; fileRef = 2439D7AE29F0757C00F07C90 /* locales */; }; 2439D7C529F0757C00F07C90 /* import-account-with-file.html in Resources */ = {isa = PBXBuildFile; fileRef = 2439D7AF29F0757C00F07C90 /* import-account-with-file.html */; }; 2439D7C629F0757C00F07C90 /* sdk.bundle.js in Resources */ = {isa = PBXBuildFile; fileRef = 2439D7B029F0757C00F07C90 /* sdk.bundle.js */; }; @@ -31,7 +30,6 @@ 2439D7CB29F0757C00F07C90 /* logo64.png in Resources */ = {isa = PBXBuildFile; fileRef = 2439D7B529F0757C00F07C90 /* logo64.png */; }; 2439D7CC29F0757C00F07C90 /* signatureRequest.bundle.js in Resources */ = {isa = PBXBuildFile; fileRef = 2439D7B629F0757C00F07C90 /* signatureRequest.bundle.js */; }; 2439D7CD29F0757C00F07C90 /* importAccountWithFile.bundle.js in Resources */ = {isa = PBXBuildFile; fileRef = 2439D7B729F0757C00F07C90 /* importAccountWithFile.bundle.js */; }; - 2439D7CE29F0757C00F07C90 /* 125.bundle.js in Resources */ = {isa = PBXBuildFile; fileRef = 2439D7B829F0757C00F07C90 /* 125.bundle.js */; }; 2439D7CF29F0757C00F07C90 /* manifest.json in Resources */ = {isa = PBXBuildFile; fileRef = 2439D7B929F0757C00F07C90 /* manifest.json */; }; 2439D7D029F0757C00F07C90 /* onboarding.bundle.js in Resources */ = {isa = PBXBuildFile; fileRef = 2439D7BA29F0757C00F07C90 /* onboarding.bundle.js */; }; 2439D7D129F0757C00F07C90 /* assets in Resources */ = {isa = PBXBuildFile; fileRef = 2439D7BB29F0757C00F07C90 /* assets */; }; @@ -40,6 +38,13 @@ 2439D7D429F0757C00F07C90 /* onboarding.html in Resources */ = {isa = PBXBuildFile; fileRef = 2439D7BE29F0757C00F07C90 /* onboarding.html */; }; 2439D7D529F0757C00F07C90 /* signature-request.html in Resources */ = {isa = PBXBuildFile; fileRef = 2439D7BF29F0757C00F07C90 /* signature-request.html */; }; B3867CAE2A7B038000BD01B7 /* declarative_net_request_rules.json in Resources */ = {isa = PBXBuildFile; fileRef = B3867CAC2A7B038000BD01B7 /* declarative_net_request_rules.json */; }; + B3C8A9332C21AF6D006B8DA3 /* 314.bundle.js in Resources */ = {isa = PBXBuildFile; fileRef = B3C8A92C2C21AF6C006B8DA3 /* 314.bundle.js */; }; + B3C8A9342C21AF6D006B8DA3 /* 355.bundle.js in Resources */ = {isa = PBXBuildFile; fileRef = B3C8A92D2C21AF6C006B8DA3 /* 355.bundle.js */; }; + B3C8A9352C21AF6D006B8DA3 /* 83.bundle.js in Resources */ = {isa = PBXBuildFile; fileRef = B3C8A92E2C21AF6C006B8DA3 /* 83.bundle.js */; }; + B3C8A9362C21AF6D006B8DA3 /* 176.bundle.js in Resources */ = {isa = PBXBuildFile; fileRef = B3C8A92F2C21AF6C006B8DA3 /* 176.bundle.js */; }; + B3C8A9372C21AF6D006B8DA3 /* 878.bundle.js in Resources */ = {isa = PBXBuildFile; fileRef = B3C8A9302C21AF6C006B8DA3 /* 878.bundle.js */; }; + B3C8A9382C21AF6D006B8DA3 /* 564.bundle.js in Resources */ = {isa = PBXBuildFile; fileRef = B3C8A9312C21AF6C006B8DA3 /* 564.bundle.js */; }; + B3C8A9392C21AF6D006B8DA3 /* 467.bundle.js in Resources */ = {isa = PBXBuildFile; fileRef = B3C8A9322C21AF6C006B8DA3 /* 467.bundle.js */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -86,7 +91,6 @@ 2439D7AA29F0757C00F07C90 /* connectToApp.bundle.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; name = connectToApp.bundle.js; path = ../../../build/firefox/connectToApp.bundle.js; sourceTree = ""; }; 2439D7AB29F0757C00F07C90 /* logo16.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = logo16.png; path = ../../../build/firefox/logo16.png; sourceTree = ""; }; 2439D7AC29F0757C00F07C90 /* connect-to-app.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; name = "connect-to-app.html"; path = "../../../build/firefox/connect-to-app.html"; sourceTree = ""; }; - 2439D7AD29F0757C00F07C90 /* 909.bundle.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; name = 909.bundle.js; path = ../../../build/firefox/909.bundle.js; sourceTree = ""; }; 2439D7AE29F0757C00F07C90 /* locales */ = {isa = PBXFileReference; lastKnownFileType = folder; name = locales; path = ../../../build/firefox/locales; sourceTree = ""; }; 2439D7AF29F0757C00F07C90 /* import-account-with-file.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; name = "import-account-with-file.html"; path = "../../../build/firefox/import-account-with-file.html"; sourceTree = ""; }; 2439D7B029F0757C00F07C90 /* sdk.bundle.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; name = sdk.bundle.js; path = ../../../build/firefox/sdk.bundle.js; sourceTree = ""; }; @@ -97,7 +101,6 @@ 2439D7B529F0757C00F07C90 /* logo64.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = logo64.png; path = ../../../build/firefox/logo64.png; sourceTree = ""; }; 2439D7B629F0757C00F07C90 /* signatureRequest.bundle.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; name = signatureRequest.bundle.js; path = ../../../build/firefox/signatureRequest.bundle.js; sourceTree = ""; }; 2439D7B729F0757C00F07C90 /* importAccountWithFile.bundle.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; name = importAccountWithFile.bundle.js; path = ../../../build/firefox/importAccountWithFile.bundle.js; sourceTree = ""; }; - 2439D7B829F0757C00F07C90 /* 125.bundle.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; name = 125.bundle.js; path = ../../../build/firefox/125.bundle.js; sourceTree = ""; }; 2439D7B929F0757C00F07C90 /* manifest.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; name = manifest.json; path = ../../../build/firefox/manifest.json; sourceTree = ""; }; 2439D7BA29F0757C00F07C90 /* onboarding.bundle.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; name = onboarding.bundle.js; path = ../../../build/firefox/onboarding.bundle.js; sourceTree = ""; }; 2439D7BB29F0757C00F07C90 /* assets */ = {isa = PBXFileReference; lastKnownFileType = folder; name = assets; path = ../../../build/firefox/assets; sourceTree = ""; }; @@ -106,6 +109,13 @@ 2439D7BE29F0757C00F07C90 /* onboarding.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; name = onboarding.html; path = ../../../build/firefox/onboarding.html; sourceTree = ""; }; 2439D7BF29F0757C00F07C90 /* signature-request.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; name = "signature-request.html"; path = "../../../build/firefox/signature-request.html"; sourceTree = ""; }; B3867CAC2A7B038000BD01B7 /* declarative_net_request_rules.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = declarative_net_request_rules.json; path = ../../../src/declarative_net_request_rules.json; sourceTree = ""; }; + B3C8A92C2C21AF6C006B8DA3 /* 314.bundle.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; name = 314.bundle.js; path = ../../../build/firefox/314.bundle.js; sourceTree = ""; }; + B3C8A92D2C21AF6C006B8DA3 /* 355.bundle.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; name = 355.bundle.js; path = ../../../build/firefox/355.bundle.js; sourceTree = ""; }; + B3C8A92E2C21AF6C006B8DA3 /* 83.bundle.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; name = 83.bundle.js; path = ../../../build/firefox/83.bundle.js; sourceTree = ""; }; + B3C8A92F2C21AF6C006B8DA3 /* 176.bundle.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; name = 176.bundle.js; path = ../../../build/firefox/176.bundle.js; sourceTree = ""; }; + B3C8A9302C21AF6C006B8DA3 /* 878.bundle.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; name = 878.bundle.js; path = ../../../build/firefox/878.bundle.js; sourceTree = ""; }; + B3C8A9312C21AF6C006B8DA3 /* 564.bundle.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; name = 564.bundle.js; path = ../../../build/firefox/564.bundle.js; sourceTree = ""; }; + B3C8A9322C21AF6C006B8DA3 /* 467.bundle.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; name = 467.bundle.js; path = ../../../build/firefox/467.bundle.js; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -184,11 +194,17 @@ 2439D7A929F0757C00F07C90 /* Resources */ = { isa = PBXGroup; children = ( + B3C8A92E2C21AF6C006B8DA3 /* 83.bundle.js */, + B3C8A92F2C21AF6C006B8DA3 /* 176.bundle.js */, + B3C8A92C2C21AF6C006B8DA3 /* 314.bundle.js */, + B3C8A92D2C21AF6C006B8DA3 /* 355.bundle.js */, + B3C8A9322C21AF6C006B8DA3 /* 467.bundle.js */, + B3C8A9312C21AF6C006B8DA3 /* 564.bundle.js */, + B3C8A9302C21AF6C006B8DA3 /* 878.bundle.js */, B3867CAC2A7B038000BD01B7 /* declarative_net_request_rules.json */, 2439D7AA29F0757C00F07C90 /* connectToApp.bundle.js */, 2439D7AB29F0757C00F07C90 /* logo16.png */, 2439D7AC29F0757C00F07C90 /* connect-to-app.html */, - 2439D7AD29F0757C00F07C90 /* 909.bundle.js */, 2439D7AE29F0757C00F07C90 /* locales */, 2439D7AF29F0757C00F07C90 /* import-account-with-file.html */, 2439D7B029F0757C00F07C90 /* sdk.bundle.js */, @@ -199,7 +215,6 @@ 2439D7B529F0757C00F07C90 /* logo64.png */, 2439D7B629F0757C00F07C90 /* signatureRequest.bundle.js */, 2439D7B729F0757C00F07C90 /* importAccountWithFile.bundle.js */, - 2439D7B829F0757C00F07C90 /* 125.bundle.js */, 2439D7B929F0757C00F07C90 /* manifest.json */, 2439D7BA29F0757C00F07C90 /* onboarding.bundle.js */, 2439D7BB29F0757C00F07C90 /* assets */, @@ -310,25 +325,30 @@ 2439D7CB29F0757C00F07C90 /* logo64.png in Resources */, 2439D7D229F0757C00F07C90 /* popup.bundle.js in Resources */, 2439D7C029F0757C00F07C90 /* connectToApp.bundle.js in Resources */, - 2439D7CE29F0757C00F07C90 /* 125.bundle.js in Resources */, 2439D7D129F0757C00F07C90 /* assets in Resources */, 2439D7C129F0757C00F07C90 /* logo16.png in Resources */, 2439D7C729F0757C00F07C90 /* contentScript.bundle.js in Resources */, 2439D7D329F0757C00F07C90 /* logo192.png in Resources */, 2439D7C429F0757C00F07C90 /* locales in Resources */, 2439D7D029F0757C00F07C90 /* onboarding.bundle.js in Resources */, + B3C8A9352C21AF6D006B8DA3 /* 83.bundle.js in Resources */, + B3C8A9342C21AF6D006B8DA3 /* 355.bundle.js in Resources */, 2439D7C829F0757C00F07C90 /* popup.html in Resources */, 2439D7C929F0757C00F07C90 /* background.bundle.js in Resources */, 2439D7C529F0757C00F07C90 /* import-account-with-file.html in Resources */, - 2439D7C329F0757C00F07C90 /* 909.bundle.js in Resources */, 2439D7C629F0757C00F07C90 /* sdk.bundle.js in Resources */, 2439D7CD29F0757C00F07C90 /* importAccountWithFile.bundle.js in Resources */, + B3C8A9392C21AF6D006B8DA3 /* 467.bundle.js in Resources */, + B3C8A9362C21AF6D006B8DA3 /* 176.bundle.js in Resources */, 2439D7CF29F0757C00F07C90 /* manifest.json in Resources */, + B3C8A9332C21AF6D006B8DA3 /* 314.bundle.js in Resources */, 2439D7CC29F0757C00F07C90 /* signatureRequest.bundle.js in Resources */, B3867CAE2A7B038000BD01B7 /* declarative_net_request_rules.json in Resources */, + B3C8A9382C21AF6D006B8DA3 /* 564.bundle.js in Resources */, 2439D7CA29F0757C00F07C90 /* logo128.png in Resources */, 2439D7C229F0757C00F07C90 /* connect-to-app.html in Resources */, 2439D7D529F0757C00F07C90 /* signature-request.html in Resources */, + B3C8A9372C21AF6D006B8DA3 /* 878.bundle.js in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -564,7 +584,7 @@ CODE_SIGN_ENTITLEMENTS = "Casper Wallet/Casper Wallet.entitlements"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 64; + CURRENT_PROJECT_VERSION = 69; ENABLE_HARDENED_RUNTIME = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "Casper Wallet/Info.plist"; @@ -578,7 +598,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.14; - MARKETING_VERSION = 1.10.0; + MARKETING_VERSION = 1.11.0; OTHER_LDFLAGS = ( "-framework", SafariServices, @@ -601,7 +621,7 @@ CODE_SIGN_ENTITLEMENTS = "Casper Wallet/Casper Wallet.entitlements"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 64; + CURRENT_PROJECT_VERSION = 69; ENABLE_HARDENED_RUNTIME = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "Casper Wallet/Info.plist"; @@ -615,7 +635,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.14; - MARKETING_VERSION = 1.10.0; + MARKETING_VERSION = 1.11.0; OTHER_LDFLAGS = ( "-framework", SafariServices,