From 507f3b34da6003e9f2f85dc38514af6f48efbc54 Mon Sep 17 00:00:00 2001 From: Vynnyk Dmytro Date: Wed, 22 Nov 2023 16:53:31 +0200 Subject: [PATCH] Release/1.7.0 (#866) * added listener for window closing (#752) Co-authored-by: ost-ptk Co-authored-by: Vynnyk Dmytro * fix displaying amount in CSPR only for Casper (#753) Co-authored-by: ost-ptk Co-authored-by: Vynnyk Dmytro * UI changed based on a new design (#754) Co-authored-by: ost-ptk Co-authored-by: Vynnyk Dmytro * fixed skeleton for NFT image and made small changes (#755) Co-authored-by: ost-ptk Co-authored-by: Vynnyk Dmytro * feature: add remembering scroll position in the NFT, Deploys and Tokens list (#759) * added remembering scroll position in the NFT list and removed background fetch for nft * added remembering scroll position in the NFT list and removed background fetch for nft * added remembering scroll position in the Deploys list, removed background fetch for deploys and made some improvements * small improvements * added remembering scroll position in the Casper token activity list and small improvements * added remembering scroll position in the ERC20 token activity list and small improvements * removed duplication and small fix * fixed infinity scroll for NFT * fixed infinity scroll for Deploys tab * fixed infinity scroll for Casper token activity * fixed infinity scroll for ERC20 token activity * unused infinite scroll hook removed * updated error handler for account deploys and NFT tokens request --------- Co-authored-by: ost-ptk * illustrations are updated (#762) Co-authored-by: ost-ptk Co-authored-by: Vynnyk Dmytro * fix: fix native transfer in Firefox (#763) * added a check for deploy hash before the extended deploys info request * updated manifest permission for Firefox * removed comment --------- Co-authored-by: ost-ptk Co-authored-by: Vynnyk Dmytro * added tooltip on hover hash (#764) Co-authored-by: ost-ptk Co-authored-by: Vynnyk Dmytro * Redesign list of NFTs (#766) * Redesign list of NFTs * Fix PR issues and add useAsyncEffect hook * Fix issue with audio loading state * Fix issue with deploy timestamp (#768) * Release 1.5.2 version (#777) * feature: add NFT token transfer (#769) * added UI for NFT token transfer flow and business logic draft * added nft transfer logic * added fix for deploy timestamp issue * removed unused packages * removed back button from success screen --------- Co-authored-by: ost-ptk Co-authored-by: Vynnyk Dmytro * fix issues with the layout of a deploy list (#772) Co-authored-by: ost-ptk Co-authored-by: Vynnyk Dmytro * build(deps): bump sqlite3 from 5.0.8 to 5.1.6 (#694) Bumps [sqlite3](https://github.com/TryGhost/node-sqlite3) from 5.0.8 to 5.1.6. - [Release notes](https://github.com/TryGhost/node-sqlite3/releases) - [Commits](https://github.com/TryGhost/node-sqlite3/compare/v5.0.8...v5.1.6) --- updated-dependencies: - dependency-name: sqlite3 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump json5 from 1.0.1 to 1.0.2 (#695) Bumps [json5](https://github.com/json5/json5) from 1.0.1 to 1.0.2. - [Release notes](https://github.com/json5/json5/releases) - [Changelog](https://github.com/json5/json5/blob/main/CHANGELOG.md) - [Commits](https://github.com/json5/json5/compare/v1.0.1...v1.0.2) --- updated-dependencies: - dependency-name: json5 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump dns-packet from 5.3.1 to 5.6.0 (#696) Bumps [dns-packet](https://github.com/mafintosh/dns-packet) from 5.3.1 to 5.6.0. - [Changelog](https://github.com/mafintosh/dns-packet/blob/master/CHANGELOG.md) - [Commits](https://github.com/mafintosh/dns-packet/compare/v5.3.1...v5.6.0) --- updated-dependencies: - dependency-name: dns-packet dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump xml2js and web-ext (#697) Bumps [xml2js](https://github.com/Leonidas-from-XIV/node-xml2js) to 0.5.0 and updates ancestor dependency [web-ext](https://github.com/mozilla/web-ext). These dependencies need to be updated together. Updates `xml2js` from 0.4.23 to 0.5.0 - [Commits](https://github.com/Leonidas-from-XIV/node-xml2js/commits/0.5.0) Updates `web-ext` from 7.5.0 to 7.6.2 - [Release notes](https://github.com/mozilla/web-ext/releases) - [Commits](https://github.com/mozilla/web-ext/compare/7.5.0...7.6.2) --- updated-dependencies: - dependency-name: xml2js dependency-type: indirect - dependency-name: web-ext dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Dmytro Vynnyk * build(deps-dev): bump eslint-plugin-jsx-a11y from 6.6.1 to 6.7.1 (#703) Bumps [eslint-plugin-jsx-a11y](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y) from 6.6.1 to 6.7.1. - [Release notes](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/releases) - [Changelog](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/CHANGELOG.md) - [Commits](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/compare/v6.6.1...v6.7.1) --- updated-dependencies: - dependency-name: eslint-plugin-jsx-a11y dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Vynnyk Dmytro * build(deps-dev): bump fs-extra from 11.1.0 to 11.1.1 (#705) Bumps [fs-extra](https://github.com/jprichardson/node-fs-extra) from 11.1.0 to 11.1.1. - [Changelog](https://github.com/jprichardson/node-fs-extra/blob/master/CHANGELOG.md) - [Commits](https://github.com/jprichardson/node-fs-extra/compare/11.1.0...11.1.1) --- updated-dependencies: - dependency-name: fs-extra dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump i18next-conv from 13.1.0 to 14.0.0 (#706) Bumps [i18next-conv](https://github.com/i18next/i18next-gettext-converter) from 13.1.0 to 14.0.0. - [Release notes](https://github.com/i18next/i18next-gettext-converter/releases) - [Changelog](https://github.com/i18next/i18next-gettext-converter/blob/master/CHANGELOG.md) - [Commits](https://github.com/i18next/i18next-gettext-converter/compare/v13.1.0...v14.0.0) --- updated-dependencies: - dependency-name: i18next-conv dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Vynnyk Dmytro * build(deps): bump word-wrap from 1.2.3 to 1.2.4 (#731) Bumps [word-wrap](https://github.com/jonschlinkert/word-wrap) from 1.2.3 to 1.2.4. - [Release notes](https://github.com/jonschlinkert/word-wrap/releases) - [Commits](https://github.com/jonschlinkert/word-wrap/compare/1.2.3...1.2.4) --- updated-dependencies: - dependency-name: word-wrap dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump apollo-server-core from 3.11.1 to 3.12.1 (#765) Bumps [apollo-server-core](https://github.com/apollographql/apollo-server/tree/HEAD/packages/apollo-server-core) from 3.11.1 to 3.12.1. - [Release notes](https://github.com/apollographql/apollo-server/releases) - [Commits](https://github.com/apollographql/apollo-server/commits/apollo-server-core@3.12.1/packages/apollo-server-core) --- updated-dependencies: - dependency-name: apollo-server-core dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump i18next from 21.10.0 to 23.5.1 (#773) Bumps [i18next](https://github.com/i18next/i18next) from 21.10.0 to 23.5.1. - [Release notes](https://github.com/i18next/i18next/releases) - [Changelog](https://github.com/i18next/i18next/blob/master/CHANGELOG.md) - [Commits](https://github.com/i18next/i18next/compare/v21.10.0...v23.5.1) --- updated-dependencies: - dependency-name: i18next dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Vynnyk Dmytro * fix account status indicator (#775) Co-authored-by: ost-ptk Co-authored-by: Vynnyk Dmytro * build(deps-dev): bump eslint from 8.28.0 to 8.49.0 (#776) Bumps [eslint](https://github.com/eslint/eslint) from 8.28.0 to 8.49.0. - [Release notes](https://github.com/eslint/eslint/releases) - [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md) - [Commits](https://github.com/eslint/eslint/compare/v8.28.0...v8.49.0) --- updated-dependencies: - dependency-name: eslint dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Vynnyk Dmytro * build(deps): bump got and @redux-devtools/cli (#780) Bumps [got](https://github.com/sindresorhus/got) to 11.8.6 and updates ancestor dependency [@redux-devtools/cli](https://github.com/reduxjs/redux-devtools). These dependencies need to be updated together. Updates `got` from 9.6.0 to 11.8.6 - [Release notes](https://github.com/sindresorhus/got/releases) - [Commits](https://github.com/sindresorhus/got/compare/v9.6.0...v11.8.6) Updates `@redux-devtools/cli` from 1.0.7 to 3.0.1 - [Release notes](https://github.com/reduxjs/redux-devtools/releases) - [Commits](https://github.com/reduxjs/redux-devtools/compare/@redux-devtools/cli@1.0.7...v3.0.1) --- updated-dependencies: - dependency-name: got dependency-type: indirect - dependency-name: "@redux-devtools/cli" dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Vynnyk Dmytro * build(deps): bump react-player from 2.12.0 to 2.13.0 (#785) Bumps [react-player](https://github.com/CookPete/react-player) from 2.12.0 to 2.13.0. - [Changelog](https://github.com/cookpete/react-player/blob/master/CHANGELOG.md) - [Commits](https://github.com/CookPete/react-player/compare/v2.12.0...v2.13.0) --- updated-dependencies: - dependency-name: react-player dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump prettier from 2.8.0 to 3.0.3 (#786) Bumps [prettier](https://github.com/prettier/prettier) from 2.8.0 to 3.0.3. - [Release notes](https://github.com/prettier/prettier/releases) - [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md) - [Commits](https://github.com/prettier/prettier/compare/2.8.0...3.0.3) --- updated-dependencies: - dependency-name: prettier dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump redux from 4.2.0 to 4.2.1 (#787) Bumps [redux](https://github.com/reduxjs/redux) from 4.2.0 to 4.2.1. - [Release notes](https://github.com/reduxjs/redux/releases) - [Changelog](https://github.com/reduxjs/redux/blob/master/CHANGELOG.md) - [Commits](https://github.com/reduxjs/redux/compare/v4.2.0...v4.2.1) --- updated-dependencies: - dependency-name: redux dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Vynnyk Dmytro * build(deps-dev): bump webpack-dev-server from 4.11.1 to 4.15.1 (#788) Bumps [webpack-dev-server](https://github.com/webpack/webpack-dev-server) from 4.11.1 to 4.15.1. - [Release notes](https://github.com/webpack/webpack-dev-server/releases) - [Changelog](https://github.com/webpack/webpack-dev-server/blob/master/CHANGELOG.md) - [Commits](https://github.com/webpack/webpack-dev-server/compare/v4.11.1...v4.15.1) --- updated-dependencies: - dependency-name: webpack-dev-server dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump @types/chrome from 0.0.203 to 0.0.246 (#791) Bumps [@types/chrome](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/chrome) from 0.0.203 to 0.0.246. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/chrome) --- updated-dependencies: - dependency-name: "@types/chrome" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * added message for not supporting reverse look-up modality (#783) Co-authored-by: ost-ptk Co-authored-by: Vynnyk Dmytro * added a placeholder for the contract logo (#784) Co-authored-by: ost-ptk Co-authored-by: Vynnyk Dmytro * fix: fix issue with prettier config (#792) * fixed issue with prettier config * fixed code style errors --------- Co-authored-by: ost-ptk * added new UI for erc-20 action displaying (#790) Co-authored-by: ost-ptk Co-authored-by: Vynnyk Dmytro * build(deps-dev): bump geckodriver from 3.2.0 to 4.2.1 (#793) Bumps [geckodriver](https://github.com/webdriverio-community/node-geckodriver) from 3.2.0 to 4.2.1. - [Release notes](https://github.com/webdriverio-community/node-geckodriver/releases) - [Changelog](https://github.com/webdriverio-community/node-geckodriver/blob/main/CHANGELOG.md) - [Commits](https://github.com/webdriverio-community/node-geckodriver/compare/v3.2.0...v4.2.1) --- updated-dependencies: - dependency-name: geckodriver dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump react-query from 3.39.2 to 3.39.3 (#794) Bumps [react-query](https://github.com/tannerlinsley/react-query) from 3.39.2 to 3.39.3. - [Release notes](https://github.com/tannerlinsley/react-query/releases) - [Commits](https://github.com/tannerlinsley/react-query/commits) --- updated-dependencies: - dependency-name: react-query dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Vynnyk Dmytro * build(deps): bump react-router-dom from 6.4.4 to 6.16.0 (#795) Bumps [react-router-dom](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dom) from 6.4.4 to 6.16.0. - [Release notes](https://github.com/remix-run/react-router/releases) - [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router-dom/CHANGELOG.md) - [Commits](https://github.com/remix-run/react-router/commits/react-router-dom@6.16.0/packages/react-router-dom) --- updated-dependencies: - dependency-name: react-router-dom dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Vynnyk Dmytro * build(deps): bump @scure/bip39 from 1.1.0 to 1.2.1 (#796) Bumps [@scure/bip39](https://github.com/paulmillr/scure-bip39) from 1.1.0 to 1.2.1. - [Release notes](https://github.com/paulmillr/scure-bip39/releases) - [Commits](https://github.com/paulmillr/scure-bip39/compare/1.1.0...1.2.1) --- updated-dependencies: - dependency-name: "@scure/bip39" dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Vynnyk Dmytro * build(deps-dev): bump electron from 25.8.0 to 25.8.1 (#798) Bumps [electron](https://github.com/electron/electron) from 25.8.0 to 25.8.1. - [Release notes](https://github.com/electron/electron/releases) - [Changelog](https://github.com/electron/electron/blob/main/docs/breaking-changes.md) - [Commits](https://github.com/electron/electron/compare/v25.8.0...v25.8.1) --- updated-dependencies: - dependency-name: electron dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Vynnyk Dmytro * build(deps-dev): bump lint-staged from 13.0.4 to 14.0.1 (#800) Bumps [lint-staged](https://github.com/okonet/lint-staged) from 13.0.4 to 14.0.1. - [Release notes](https://github.com/okonet/lint-staged/releases) - [Changelog](https://github.com/okonet/lint-staged/blob/master/CHANGELOG.md) - [Commits](https://github.com/okonet/lint-staged/compare/v13.0.4...v14.0.1) --- updated-dependencies: - dependency-name: lint-staged dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump tsconfig-paths-webpack-plugin from 4.0.0 to 4.1.0 (#801) Bumps [tsconfig-paths-webpack-plugin](https://github.com/dividab/tsconfig-paths-webpack-plugin) from 4.0.0 to 4.1.0. - [Changelog](https://github.com/dividab/tsconfig-paths-webpack-plugin/blob/master/CHANGELOG.md) - [Commits](https://github.com/dividab/tsconfig-paths-webpack-plugin/compare/v4.0.0...v4.1.0) --- updated-dependencies: - dependency-name: tsconfig-paths-webpack-plugin dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Vynnyk Dmytro * build(deps-dev): bump @types/facepaint from 1.2.2 to 1.2.3 (#803) Bumps [@types/facepaint](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/facepaint) from 1.2.2 to 1.2.3. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/facepaint) --- updated-dependencies: - dependency-name: "@types/facepaint" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Vynnyk Dmytro * build(deps-dev): bump webpack-cli from 5.0.0 to 5.1.4 (#804) Bumps [webpack-cli](https://github.com/webpack/webpack-cli) from 5.0.0 to 5.1.4. - [Release notes](https://github.com/webpack/webpack-cli/releases) - [Changelog](https://github.com/webpack/webpack-cli/blob/master/CHANGELOG.md) - [Commits](https://github.com/webpack/webpack-cli/compare/webpack-cli@5.0.0...webpack-cli@5.1.4) --- updated-dependencies: - dependency-name: webpack-cli dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Vynnyk Dmytro * removed input type from the transaction fee fields (#807) Co-authored-by: ost-ptk Co-authored-by: Vynnyk Dmytro * changed method of creating Keys for signing (#808) Co-authored-by: ost-ptk Co-authored-by: Vynnyk Dmytro * fix: fix scope of bugs (#809) * fixed issue with transfer NFT error after wallet unlocking and some improvements * fixed issue with empty NFT details page after wallet unlocking and some improvements * fixed issue with home screen scroll * possible fix for an issue with a gap above the sticky tabs container * fixed an issue with the scrollable Import account screen --------- Co-authored-by: ost-ptk Co-authored-by: Vynnyk Dmytro * fixed issue with the navigation menu screen scroll (#818) Co-authored-by: ost-ptk * casper logo updated (#817) Co-authored-by: ost-ptk * removed the tooltip with the full public key when the user has only one account (#819) Co-authored-by: ost-ptk * build(deps-dev): bump electron from 25.8.1 to 25.9.0 (#822) Bumps [electron](https://github.com/electron/electron) from 25.8.1 to 25.9.0. - [Release notes](https://github.com/electron/electron/releases) - [Changelog](https://github.com/electron/electron/blob/main/docs/breaking-changes.md) - [Commits](https://github.com/electron/electron/compare/v25.8.1...v25.9.0) --- updated-dependencies: - dependency-name: electron dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * disabled send button on NFT details page after this NFT was sent (#824) Co-authored-by: ost-ptk * fixed issue with the scroll on home page (#825) Co-authored-by: ost-ptk * Release 1.6.0 version (#826) * added countdown for user password length (#806) Co-authored-by: ost-ptk Co-authored-by: Vynnyk Dmytro * feature: add change password page (#799) * fixed issue with prettier config * fixed code style errors * added change password page and small refactor * merge conflict fixed * merge conflict issues fixed --------- Co-authored-by: ost-ptk * added a checkbox for setting the max transfer amount for CSPR transfer and some refactoring (#805) Co-authored-by: ost-ptk Co-authored-by: Vynnyk Dmytro * fixed issue with words highlighting in the confirm secret phrase view (#823) Co-authored-by: ost-ptk Co-authored-by: Vynnyk Dmytro * feature: add dark mode (#821) * added dark mode * merge conflict issues fixed * changed png images to svg * Fix DefaultTheme interface --------- Co-authored-by: ost-ptk Co-authored-by: Dmytro Vynnyk * build(deps): bump @scure/bip32 from 1.1.1 to 1.3.2 (#812) Bumps [@scure/bip32](https://github.com/paulmillr/scure-bip32) from 1.1.1 to 1.3.2. - [Release notes](https://github.com/paulmillr/scure-bip32/releases) - [Commits](https://github.com/paulmillr/scure-bip32/compare/1.1.1...1.3.2) --- updated-dependencies: - dependency-name: "@scure/bip32" dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump webpack from 5.76.0 to 5.88.2 (#813) Bumps [webpack](https://github.com/webpack/webpack) from 5.76.0 to 5.88.2. - [Release notes](https://github.com/webpack/webpack/releases) - [Commits](https://github.com/webpack/webpack/compare/v5.76.0...v5.88.2) --- updated-dependencies: - dependency-name: webpack dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump eslint-plugin-react from 7.31.11 to 7.33.2 (#814) Bumps [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) from 7.31.11 to 7.33.2. - [Release notes](https://github.com/jsx-eslint/eslint-plugin-react/releases) - [Changelog](https://github.com/jsx-eslint/eslint-plugin-react/blob/master/CHANGELOG.md) - [Commits](https://github.com/jsx-eslint/eslint-plugin-react/compare/v7.31.11...v7.33.2) --- updated-dependencies: - dependency-name: eslint-plugin-react dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump @babel/preset-env from 7.20.2 to 7.23.2 (#828) Bumps [@babel/preset-env](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-env) from 7.20.2 to 7.23.2. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v7.23.2/packages/babel-preset-env) --- updated-dependencies: - dependency-name: "@babel/preset-env" dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Improve skeleton for dark mode (#835) * Release 1.6.1 version (#837) * fixed issue with insufficient data for displaying erc-20 tokens (#840) Co-authored-by: ost-ptk * Update README.md to add reference to CSPR.click for integration (#842) * Improve erc20 tokens fetching (#843) * Release/1.6.2 (#844) * Improve erc20 tokens fetching * Release 1.6.2 version * feature: add QR code option to the settings menu (#774) * added QR code option to the settings menu * added QR generation page and removed unused props * merge conflict issue fixed * temp commit * Get symbol and decimal optional * Add qr code generation for sync wallet * UI and UX fixes, and fixed validation rule * moved qr generation under password protection and removed one time password * Improve qrCode generation * Add multi qr code presenting --------- Co-authored-by: ost-ptk Co-authored-by: Vynnyk Dmytro * Release 1.6.3 version (#852) * build(deps-dev): bump ts-jest from 29.0.3 to 29.1.1 (#831) Bumps [ts-jest](https://github.com/kulshekhar/ts-jest) from 29.0.3 to 29.1.1. - [Release notes](https://github.com/kulshekhar/ts-jest/releases) - [Changelog](https://github.com/kulshekhar/ts-jest/blob/main/CHANGELOG.md) - [Commits](https://github.com/kulshekhar/ts-jest/compare/v29.0.3...v29.1.1) --- updated-dependencies: - dependency-name: ts-jest dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump redux-saga from 1.2.1 to 1.2.3 (#832) Bumps [redux-saga](https://github.com/redux-saga/redux-saga) from 1.2.1 to 1.2.3. - [Release notes](https://github.com/redux-saga/redux-saga/releases) - [Changelog](https://github.com/redux-saga/redux-saga/blob/main/CHANGELOG.md) - [Commits](https://github.com/redux-saga/redux-saga/compare/redux-saga@1.2.1...redux-saga@1.2.3) --- updated-dependencies: - dependency-name: redux-saga dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump css-loader from 6.7.2 to 6.8.1 (#833) Bumps [css-loader](https://github.com/webpack-contrib/css-loader) from 6.7.2 to 6.8.1. - [Release notes](https://github.com/webpack-contrib/css-loader/releases) - [Changelog](https://github.com/webpack-contrib/css-loader/blob/master/CHANGELOG.md) - [Commits](https://github.com/webpack-contrib/css-loader/compare/v6.7.2...v6.8.1) --- updated-dependencies: - dependency-name: css-loader dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * feature: add delegation and undelegation flow (#846) * added delegation and undelegation flow * fixed issue with sticky header in tabs * added a new modal window with buttons on the home page * added empty state UI for undelegation * fixed deploys tab and deploys details page for staking --------- Co-authored-by: ost-ptk Co-authored-by: Vynnyk Dmytro * added active address in top nav (#847) Co-authored-by: ost-ptk Co-authored-by: Vynnyk Dmytro * fixed issue with an open modal window (#848) Co-authored-by: ost-ptk Co-authored-by: Vynnyk Dmytro * build(deps-dev): bump @babel/core from 7.20.5 to 7.23.3 (#850) Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.20.5 to 7.23.3. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v7.23.3/packages/babel-core) --- updated-dependencies: - dependency-name: "@babel/core" dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump @babel/traverse from 7.20.5 to 7.23.3 (#853) Bumps [@babel/traverse](https://github.com/babel/babel/tree/HEAD/packages/babel-traverse) from 7.20.5 to 7.23.3. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v7.23.3/packages/babel-traverse) --- updated-dependencies: - dependency-name: "@babel/traverse" dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump axios from 1.1.3 to 1.6.1 (#854) Bumps [axios](https://github.com/axios/axios) from 1.1.3 to 1.6.1. - [Release notes](https://github.com/axios/axios/releases) - [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md) - [Commits](https://github.com/axios/axios/compare/v1.1.3...v1.6.1) --- updated-dependencies: - dependency-name: axios dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump i18next-http-backend from 1.4.5 to 2.4.1 (#856) Bumps [i18next-http-backend](https://github.com/i18next/i18next-http-backend) from 1.4.5 to 2.4.1. - [Changelog](https://github.com/i18next/i18next-http-backend/blob/master/CHANGELOG.md) - [Commits](https://github.com/i18next/i18next-http-backend/compare/v1.4.5...v2.4.1) --- updated-dependencies: - dependency-name: i18next-http-backend dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump chromedriver from 107.0.3 to 119.0.1 (#851) Bumps [chromedriver](https://github.com/giggio/node-chromedriver) from 107.0.3 to 119.0.1. - [Commits](https://github.com/giggio/node-chromedriver/compare/107.0.3...119.0.1) --- updated-dependencies: - dependency-name: chromedriver dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Release 1.7.0 version --------- Signed-off-by: dependabot[bot] Co-authored-by: Ostap Piatkovskyi <44294945+ost-ptk@users.noreply.github.com> Co-authored-by: ost-ptk Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Muhammet Kara --- package-lock.json | 503 +++++++----- package.json | 14 +- src/apps/popup/app-router.tsx | 3 + .../popup/pages/activity-details/content.tsx | 29 +- .../components/more-buttons-modal/index.tsx | 49 ++ .../more-buttons-modal/modal-buttons.tsx | 119 +++ .../home/components/tokens-list/index.tsx | 6 +- src/apps/popup/pages/home/index.tsx | 56 +- src/apps/popup/pages/stakes/amount-step.tsx | 143 ++++ src/apps/popup/pages/stakes/confirm-step.tsx | 134 ++++ src/apps/popup/pages/stakes/content.tsx | 131 ++++ src/apps/popup/pages/stakes/index.tsx | 412 ++++++++++ .../popup/pages/stakes/no-delegations.tsx | 34 + .../popup/pages/stakes/validator-step.tsx | 29 + src/apps/popup/pages/transfer-nft/index.tsx | 2 +- src/apps/popup/pages/transfer/content.tsx | 2 +- src/apps/popup/pages/transfer/index.tsx | 4 +- src/apps/popup/router/paths.ts | 4 +- src/apps/popup/router/types.ts | 4 +- src/assets/icons/burn.svg | 3 + src/assets/icons/delegate.svg | 3 + src/assets/icons/empty-state.svg | 739 ++++++++++++++++++ src/assets/icons/undelegate.svg | 3 + src/background/index.ts | 43 + src/background/redux/settings/selectors.ts | 7 +- src/background/service-message.ts | 18 +- src/constants.ts | 88 ++- .../header/header-connection-status.tsx | 33 +- .../layout/header/header-network-switcher.tsx | 3 +- .../header/header-submenu-bar-nav-link.tsx | 42 +- src/libs/layout/header/index.tsx | 9 +- .../account-activity-service/types.ts | 1 + src/libs/services/deployer-service/index.ts | 74 +- .../services/validators-service/constants.ts | 9 + src/libs/services/validators-service/index.ts | 3 + src/libs/services/validators-service/types.ts | 124 +++ .../validators-service/validators-service.ts | 75 ++ .../account-activity-plate.tsx | 86 +- .../account-casper-activity-plate.tsx | 28 +- .../components/account-list/account-list.tsx | 7 +- .../account-popover/account-popover.tsx | 42 +- src/libs/ui/components/avatar/avatar.tsx | 1 + src/libs/ui/components/button/button.tsx | 1 - src/libs/ui/components/error/error.tsx | 39 + src/libs/ui/components/hash/hash.tsx | 3 +- src/libs/ui/components/hash/utils.ts | 4 +- src/libs/ui/components/input/input.tsx | 4 +- src/libs/ui/components/list/list.tsx | 18 +- src/libs/ui/components/modal/modal.tsx | 35 +- .../recipient-plate/recipient-plate.tsx | 2 +- src/libs/ui/components/tabs/tabs.tsx | 2 +- src/libs/ui/components/tile/tile.tsx | 5 +- .../transfer-succeess-screen.tsx | 10 +- .../ui/components/typography/typography.tsx | 11 +- .../validator-dropdown-input.tsx | 231 ++++++ .../validator-plate/validator-plate.tsx | 188 +++++ src/libs/ui/forms/form-validation-rules.ts | 122 ++- src/libs/ui/forms/stakes-form.ts | 50 ++ src/libs/ui/forms/transfer.ts | 4 +- src/libs/ui/index.ts | 3 + .../Casper Wallet.xcodeproj/project.pbxproj | 8 +- 61 files changed, 3443 insertions(+), 416 deletions(-) create mode 100644 src/apps/popup/pages/home/components/more-buttons-modal/index.tsx create mode 100644 src/apps/popup/pages/home/components/more-buttons-modal/modal-buttons.tsx create mode 100644 src/apps/popup/pages/stakes/amount-step.tsx create mode 100644 src/apps/popup/pages/stakes/confirm-step.tsx create mode 100644 src/apps/popup/pages/stakes/content.tsx create mode 100644 src/apps/popup/pages/stakes/index.tsx create mode 100644 src/apps/popup/pages/stakes/no-delegations.tsx create mode 100644 src/apps/popup/pages/stakes/validator-step.tsx create mode 100644 src/assets/icons/burn.svg create mode 100644 src/assets/icons/delegate.svg create mode 100644 src/assets/icons/empty-state.svg create mode 100644 src/assets/icons/undelegate.svg create mode 100644 src/libs/services/validators-service/constants.ts create mode 100644 src/libs/services/validators-service/index.ts create mode 100644 src/libs/services/validators-service/types.ts create mode 100644 src/libs/services/validators-service/validators-service.ts create mode 100644 src/libs/ui/components/error/error.tsx create mode 100644 src/libs/ui/components/validator-dropdown-input/validator-dropdown-input.tsx create mode 100644 src/libs/ui/components/validator-plate/validator-plate.tsx create mode 100644 src/libs/ui/forms/stakes-form.ts diff --git a/package-lock.json b/package-lock.json index 12719ed3f..9b2d434c4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,7 +27,7 @@ "facepaint": "^1.2.1", "i18next": "^23.5.1", "i18next-browser-languagedetector": "^6.1.5", - "i18next-http-backend": "^1.4.0", + "i18next-http-backend": "^2.4.1", "i18next-parser": "^6.3.0", "lodash.throttle": "4.1.1", "mac-scrollbar": "^0.10.3", @@ -47,14 +47,14 @@ "react-redux": "8.0.5", "react-router-dom": "6.16.0", "redux": "4.2.1", - "redux-saga": "1.2.1", + "redux-saga": "1.2.3", "reselect": "4.1.7", "styled-components": "5.3.6", "typesafe-actions": "5.1.0", "yup": "^0.32.11" }, "devDependencies": { - "@babel/core": "7.20.5", + "@babel/core": "7.23.3", "@babel/plugin-proposal-class-properties": "7.18.6", "@babel/preset-env": "7.23.2", "@babel/preset-react": "7.18.6", @@ -79,10 +79,10 @@ "babel-eslint": "^10.1.0", "babel-loader": "9.1.0", "babel-preset-react-app": "^10.0.0", - "chromedriver": "^107.0.3", + "chromedriver": "^119.0.1", "concurrently": "7.6.0", "copy-webpack-plugin": "^11.0.0", - "css-loader": "6.7.2", + "css-loader": "6.8.1", "css-to-xpath": "^0.1.0", "eslint": "8.49.0", "eslint-config-airbnb": "^19.0.4", @@ -112,7 +112,7 @@ "source-map-loader": "4.0.1", "style-loader": "^3.3.1", "terser-webpack-plugin": "5.3.6", - "ts-jest": "29.0.3", + "ts-jest": "29.1.1", "ts-loader": "9.4.2", "ts-node": "10.9.1", "tsconfig-paths-webpack-plugin": "^4.1.0", @@ -450,26 +450,26 @@ } }, "node_modules/@babel/core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.5.tgz", - "integrity": "sha512-UdOWmk4pNWTm/4DlPUl/Pt4Gz4rcEMb7CY0Y3eJl5Yz1vI8ZJGmHWaVE55LoxRjdpx0z259GE9U5STA9atUinQ==", - "dev": true, - "dependencies": { - "@ampproject/remapping": "^2.1.0", - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.20.5", - "@babel/helper-compilation-targets": "^7.20.0", - "@babel/helper-module-transforms": "^7.20.2", - "@babel/helpers": "^7.20.5", - "@babel/parser": "^7.20.5", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.20.5", - "@babel/types": "^7.20.5", - "convert-source-map": "^1.7.0", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.3.tgz", + "integrity": "sha512-Jg+msLuNuCJDyBvFv5+OKOUjWMZgd85bKjbICd3zWrKAo+bJ49HJufi7CQE0q0uR8NGyO6xkCACScNqyjHSZew==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.3", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helpers": "^7.23.2", + "@babel/parser": "^7.23.3", + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.23.3", + "@babel/types": "^7.23.3", + "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", - "json5": "^2.2.1", - "semver": "^6.3.0" + "json5": "^2.2.3", + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" @@ -479,6 +479,12 @@ "url": "https://opencollective.com/babel" } }, + "node_modules/@babel/core/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, "node_modules/@babel/eslint-parser": { "version": "7.17.0", "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.17.0.tgz", @@ -529,12 +535,13 @@ } }, "node_modules/@babel/generator": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.5.tgz", - "integrity": "sha512-jl7JY2Ykn9S0yj4DQP82sYvPU+T3g0HFcWTqDLqiuA9tGRNIj9VfbtXGAYTTkyNEnQk1jkMGOdYka8aG/lulCA==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.3.tgz", + "integrity": "sha512-keeZWAV4LU3tW0qRi19HRpabC/ilM0HRBBzf9/k8FFiG4KVpiv0FIy4hHfLfFQZNhziCTPTmd59zoyv6DNISzg==", "dependencies": { - "@babel/types": "^7.20.5", + "@babel/types": "^7.23.3", "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" }, "engines": { @@ -674,12 +681,12 @@ } }, "node_modules/@babel/helper-function-name": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", - "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dependencies": { - "@babel/template": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" @@ -720,9 +727,9 @@ } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.0.tgz", - "integrity": "sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", + "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", "dev": true, "dependencies": { "@babel/helper-environment-visitor": "^7.22.20", @@ -868,14 +875,14 @@ } }, "node_modules/@babel/helpers": { - "version": "7.20.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.6.tgz", - "integrity": "sha512-Pf/OjgfgFRW5bApskEz5pvidpim7tEDPlFtKcNRXWmfHGn9IEI2W2flqRQXTFb7gIPTyK++N6rVHuwKut4XK6w==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.2.tgz", + "integrity": "sha512-lzchcp8SjTSVe/fPmLwtWVBFC7+Tbn8LGHDVfDp9JGxpAY5opSaEFgt8UQvrnECWOTdji2mOWMz1rOhkHscmGQ==", "dev": true, "dependencies": { - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.20.5", - "@babel/types": "^7.20.5" + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.23.2", + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" @@ -895,9 +902,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", - "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.3.tgz", + "integrity": "sha512-uVsWNvlVsIninV2prNz/3lHCb+5CJ+e+IUBfbjToAHODtfGYLfCFuY4AU7TskI+dAKk+njsPiBjq1gKTvZOBaw==", "bin": { "parser": "bin/babel-parser.js" }, @@ -2478,18 +2485,18 @@ } }, "node_modules/@babel/traverse": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.5.tgz", - "integrity": "sha512-WM5ZNN3JITQIq9tFZaw1ojLU3WgWdtkxnhM1AegMS+PvHjkM5IXjmYEGY7yukz5XS4sJyEf2VzWjI8uAavhxBQ==", - "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.20.5", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.19.0", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.20.5", - "@babel/types": "^7.20.5", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.3.tgz", + "integrity": "sha512-+K0yF1/9yR0oHdE0StHuEj3uTPzwwbrLGfNOndVJVV2TqA5+j3oljJUb4nmB954FLGjNem976+B+eDuLIjesiQ==", + "dependencies": { + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.3", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.3", + "@babel/types": "^7.23.3", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -2498,9 +2505,9 @@ } }, "node_modules/@babel/types": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", - "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.3.tgz", + "integrity": "sha512-OZnvoH2l8PK5eUvEcUyCt/sXgr/h+UWpVuBbOljwcrAgUl6lpchoQ++PHGyQy1AtYnVA6CEq3y5xeEI10brpXw==", "dependencies": { "@babel/helper-string-parser": "^7.22.5", "@babel/helper-validator-identifier": "^7.22.20", @@ -5395,9 +5402,9 @@ } }, "node_modules/@redux-saga/core": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@redux-saga/core/-/core-1.2.1.tgz", - "integrity": "sha512-ABCxsZy9DwmNoYNo54ZlfuTvh77RXx8ODKpxOHeWam2dOaLGQ7vAktpfOtqSeTdYrKEORtTeWnxkGJMmPOoukg==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@redux-saga/core/-/core-1.2.3.tgz", + "integrity": "sha512-U1JO6ncFBAklFTwoQ3mjAeQZ6QGutsJzwNBjgVLSWDpZTRhobUzuVDS1qH3SKGJD8fvqoaYOjp6XJ3gCmeZWgA==", "dependencies": { "@babel/runtime": "^7.6.3", "@redux-saga/deferred": "^1.2.1", @@ -5601,9 +5608,9 @@ } }, "node_modules/@testim/chrome-version": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@testim/chrome-version/-/chrome-version-1.1.3.tgz", - "integrity": "sha512-g697J3WxV/Zytemz8aTuKjTGYtta9+02kva3C1xc7KXB8GdbfE1akGJIsZLyY/FSh2QrnE+fiB7vmWU3XNcb6A==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@testim/chrome-version/-/chrome-version-1.1.4.tgz", + "integrity": "sha512-kIhULpw9TrGYnHp/8VfdcneIcxKnLixmADtukQRtJUmsVlMg0niMkwV0xZmi8hqa57xqilIHjWFA0GKvEjVU5g==", "dev": true }, "node_modules/@testing-library/dom": { @@ -8532,9 +8539,9 @@ } }, "node_modules/axios": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.1.3.tgz", - "integrity": "sha512-00tXVRwKx/FZr/IDVFt4C+f9FYairX517WoGCL6dpOntqLkZofjhu43F/Xl44UOpqa+9sLFDrG/XAnFsUYgkDA==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.1.tgz", + "integrity": "sha512-vfBmhDpKafglh0EldBEbVuoe7DyAavGSLWhuSm5ZSEKQnHhBf0xAAwybbNH1IkrJNGnS/VG4I5yxig1pCEXE4g==", "dev": true, "dependencies": { "follow-redirects": "^1.15.0", @@ -10133,25 +10140,25 @@ } }, "node_modules/chromedriver": { - "version": "107.0.3", - "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-107.0.3.tgz", - "integrity": "sha512-jmzpZgctCRnhYAn0l/NIjP4vYN3L8GFVbterTrRr2Ly3W5rFMb9H8EKGuM5JCViPKSit8FbE718kZTEt3Yvffg==", + "version": "119.0.1", + "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-119.0.1.tgz", + "integrity": "sha512-lpCFFLaXPpvElTaUOWKdP74pFb/sJhWtWqMjn7Ju1YriWn8dT5JBk84BGXMPvZQs70WfCYWecxdMmwfIu1Mupg==", "dev": true, "hasInstallScript": true, "dependencies": { - "@testim/chrome-version": "^1.1.3", - "axios": "^1.1.3", - "compare-versions": "^5.0.1", + "@testim/chrome-version": "^1.1.4", + "axios": "^1.6.0", + "compare-versions": "^6.1.0", "extract-zip": "^2.0.1", "https-proxy-agent": "^5.0.1", "proxy-from-env": "^1.1.0", - "tcp-port-used": "^1.0.1" + "tcp-port-used": "^1.0.2" }, "bin": { "chromedriver": "bin/chromedriver" }, "engines": { - "node": ">=14" + "node": ">=18" } }, "node_modules/ci-info": { @@ -10514,9 +10521,9 @@ "dev": true }, "node_modules/compare-versions": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-5.0.1.tgz", - "integrity": "sha512-v8Au3l0b+Nwkp4G142JcgJFh1/TUhdxut7wzD1Nq1dyp5oa3tXaqb03EXOAB6jS4gMlalkjAUPZBMiAfKUixHQ==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.0.tgz", + "integrity": "sha512-LNZQXhqUvqUTotpZ00qLSaify3b4VFD588aRr8MKFw4CMUr98ytzCW5wDH5qx/DEY5kCDXcbcRuCqL0szEf2tg==", "dev": true }, "node_modules/component-emitter": { @@ -10996,11 +11003,49 @@ "dev": true }, "node_modules/cross-fetch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", - "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", + "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", + "dependencies": { + "node-fetch": "^2.6.12" + } + }, + "node_modules/cross-fetch/node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", "dependencies": { - "node-fetch": "2.6.7" + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/cross-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/cross-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/cross-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" } }, "node_modules/cross-spawn": { @@ -11069,15 +11114,15 @@ } }, "node_modules/css-loader": { - "version": "6.7.2", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.2.tgz", - "integrity": "sha512-oqGbbVcBJkm8QwmnNzrFrWTnudnRZC+1eXikLJl0n4ljcfotgRifpg2a1lKy8jTrc4/d9A/ap1GFq1jDKG7J+Q==", + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.8.1.tgz", + "integrity": "sha512-xDAXtEVGlD0gJ07iclwWVkLoZOpEvAWaSyf6W18S2pOC//K8+qUDIx8IIT3D+HjnmkJPQeesOPv5aiUaJsCM2g==", "dev": true, "dependencies": { "icss-utils": "^5.1.0", - "postcss": "^8.4.18", + "postcss": "^8.4.21", "postcss-modules-extract-imports": "^3.0.0", - "postcss-modules-local-by-default": "^4.0.0", + "postcss-modules-local-by-default": "^4.0.3", "postcss-modules-scope": "^3.0.0", "postcss-modules-values": "^4.0.0", "postcss-value-parser": "^4.2.0", @@ -16049,11 +16094,11 @@ } }, "node_modules/i18next-http-backend": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-1.4.5.tgz", - "integrity": "sha512-tLuHWuLWl6CmS07o+UB6EcQCaUjrZ1yhdseIN7sfq0u7phsMePJ8pqlGhIAdRDPF/q7ooyo5MID5DRFBCH+x5w==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-2.4.1.tgz", + "integrity": "sha512-CZHzFGDvF8zN7ya1W2lHbgLj2ejPUvPD836+vA3eNXc9eKGUM3MSF6SA2TKBXKBZ2cNG3nxzycCXeM6n/46KWQ==", "dependencies": { - "cross-fetch": "3.1.5" + "cross-fetch": "4.0.0" } }, "node_modules/i18next-parser": { @@ -22918,9 +22963,9 @@ } }, "node_modules/postcss-modules-local-by-default": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz", - "integrity": "sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.3.tgz", + "integrity": "sha512-2/u2zraspoACtrbFRnTijMiQtb4GW4BvatjaG/bCjYQo8kLTdevCUlwuBHx2sCnSyrI3x3qj4ZK1j5LQBgzmwA==", "dev": true, "dependencies": { "icss-utils": "^5.0.0", @@ -23904,11 +23949,11 @@ } }, "node_modules/redux-saga": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/redux-saga/-/redux-saga-1.2.1.tgz", - "integrity": "sha512-fVCicLlf4hLP+KB6H7RHfZlZ8LdYckhaemXBB3wh//a2ESyz/z/l8ygxlm0OqPjS/PARdsQ2hIdAltxEB+NgvA==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/redux-saga/-/redux-saga-1.2.3.tgz", + "integrity": "sha512-HDe0wTR5nhd8Xr5xjGzoyTbdAw6rjy1GDplFt3JKtKN8/MnkQSRqK/n6aQQhpw5NI4ekDVOaW+w4sdxPBaCoTQ==", "dependencies": { - "@redux-saga/core": "^1.2.1" + "@redux-saga/core": "^1.2.3" } }, "node_modules/redux-thunk": { @@ -26608,18 +26653,18 @@ } }, "node_modules/ts-jest": { - "version": "29.0.3", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.0.3.tgz", - "integrity": "sha512-Ibygvmuyq1qp/z3yTh9QTwVVAbFdDy/+4BtIQR2sp6baF2SJU/8CKK/hhnGIDY2L90Az2jIqTwZPnN2p+BweiQ==", + "version": "29.1.1", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.1.tgz", + "integrity": "sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA==", "dev": true, "dependencies": { "bs-logger": "0.x", "fast-json-stable-stringify": "2.x", "jest-util": "^29.0.0", - "json5": "^2.2.1", + "json5": "^2.2.3", "lodash.memoize": "4.x", "make-error": "1.x", - "semver": "7.x", + "semver": "^7.5.3", "yargs-parser": "^21.0.1" }, "bin": { @@ -26633,7 +26678,7 @@ "@jest/types": "^29.0.0", "babel-jest": "^29.0.0", "jest": "^29.0.0", - "typescript": ">=4.3" + "typescript": ">=4.3 <6" }, "peerDependenciesMeta": { "@babel/core": { @@ -26651,9 +26696,9 @@ } }, "node_modules/ts-jest/node_modules/semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -29295,26 +29340,34 @@ "dev": true }, "@babel/core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.5.tgz", - "integrity": "sha512-UdOWmk4pNWTm/4DlPUl/Pt4Gz4rcEMb7CY0Y3eJl5Yz1vI8ZJGmHWaVE55LoxRjdpx0z259GE9U5STA9atUinQ==", - "dev": true, - "requires": { - "@ampproject/remapping": "^2.1.0", - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.20.5", - "@babel/helper-compilation-targets": "^7.20.0", - "@babel/helper-module-transforms": "^7.20.2", - "@babel/helpers": "^7.20.5", - "@babel/parser": "^7.20.5", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.20.5", - "@babel/types": "^7.20.5", - "convert-source-map": "^1.7.0", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.3.tgz", + "integrity": "sha512-Jg+msLuNuCJDyBvFv5+OKOUjWMZgd85bKjbICd3zWrKAo+bJ49HJufi7CQE0q0uR8NGyO6xkCACScNqyjHSZew==", + "dev": true, + "requires": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.3", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helpers": "^7.23.2", + "@babel/parser": "^7.23.3", + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.23.3", + "@babel/types": "^7.23.3", + "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", - "json5": "^2.2.1", - "semver": "^6.3.0" + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "dependencies": { + "convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + } } }, "@babel/eslint-parser": { @@ -29353,12 +29406,13 @@ } }, "@babel/generator": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.5.tgz", - "integrity": "sha512-jl7JY2Ykn9S0yj4DQP82sYvPU+T3g0HFcWTqDLqiuA9tGRNIj9VfbtXGAYTTkyNEnQk1jkMGOdYka8aG/lulCA==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.3.tgz", + "integrity": "sha512-keeZWAV4LU3tW0qRi19HRpabC/ilM0HRBBzf9/k8FFiG4KVpiv0FIy4hHfLfFQZNhziCTPTmd59zoyv6DNISzg==", "requires": { - "@babel/types": "^7.20.5", + "@babel/types": "^7.23.3", "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" }, "dependencies": { @@ -29469,12 +29523,12 @@ "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==" }, "@babel/helper-function-name": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", - "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "requires": { - "@babel/template": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" } }, "@babel/helper-hoist-variables": { @@ -29503,9 +29557,9 @@ } }, "@babel/helper-module-transforms": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.0.tgz", - "integrity": "sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", + "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", "dev": true, "requires": { "@babel/helper-environment-visitor": "^7.22.20", @@ -29606,14 +29660,14 @@ } }, "@babel/helpers": { - "version": "7.20.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.6.tgz", - "integrity": "sha512-Pf/OjgfgFRW5bApskEz5pvidpim7tEDPlFtKcNRXWmfHGn9IEI2W2flqRQXTFb7gIPTyK++N6rVHuwKut4XK6w==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.2.tgz", + "integrity": "sha512-lzchcp8SjTSVe/fPmLwtWVBFC7+Tbn8LGHDVfDp9JGxpAY5opSaEFgt8UQvrnECWOTdji2mOWMz1rOhkHscmGQ==", "dev": true, "requires": { - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.20.5", - "@babel/types": "^7.20.5" + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.23.2", + "@babel/types": "^7.23.0" } }, "@babel/highlight": { @@ -29627,9 +29681,9 @@ } }, "@babel/parser": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", - "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==" + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.3.tgz", + "integrity": "sha512-uVsWNvlVsIninV2prNz/3lHCb+5CJ+e+IUBfbjToAHODtfGYLfCFuY4AU7TskI+dAKk+njsPiBjq1gKTvZOBaw==" }, "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { "version": "7.22.15", @@ -30685,26 +30739,26 @@ } }, "@babel/traverse": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.5.tgz", - "integrity": "sha512-WM5ZNN3JITQIq9tFZaw1ojLU3WgWdtkxnhM1AegMS+PvHjkM5IXjmYEGY7yukz5XS4sJyEf2VzWjI8uAavhxBQ==", - "requires": { - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.20.5", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.19.0", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.20.5", - "@babel/types": "^7.20.5", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.3.tgz", + "integrity": "sha512-+K0yF1/9yR0oHdE0StHuEj3uTPzwwbrLGfNOndVJVV2TqA5+j3oljJUb4nmB954FLGjNem976+B+eDuLIjesiQ==", + "requires": { + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.3", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.3", + "@babel/types": "^7.23.3", "debug": "^4.1.0", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", - "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.3.tgz", + "integrity": "sha512-OZnvoH2l8PK5eUvEcUyCt/sXgr/h+UWpVuBbOljwcrAgUl6lpchoQ++PHGyQy1AtYnVA6CEq3y5xeEI10brpXw==", "requires": { "@babel/helper-string-parser": "^7.22.5", "@babel/helper-validator-identifier": "^7.22.20", @@ -32927,9 +32981,9 @@ } }, "@redux-saga/core": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@redux-saga/core/-/core-1.2.1.tgz", - "integrity": "sha512-ABCxsZy9DwmNoYNo54ZlfuTvh77RXx8ODKpxOHeWam2dOaLGQ7vAktpfOtqSeTdYrKEORtTeWnxkGJMmPOoukg==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@redux-saga/core/-/core-1.2.3.tgz", + "integrity": "sha512-U1JO6ncFBAklFTwoQ3mjAeQZ6QGutsJzwNBjgVLSWDpZTRhobUzuVDS1qH3SKGJD8fvqoaYOjp6XJ3gCmeZWgA==", "requires": { "@babel/runtime": "^7.6.3", "@redux-saga/deferred": "^1.2.1", @@ -33094,9 +33148,9 @@ } }, "@testim/chrome-version": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@testim/chrome-version/-/chrome-version-1.1.3.tgz", - "integrity": "sha512-g697J3WxV/Zytemz8aTuKjTGYtta9+02kva3C1xc7KXB8GdbfE1akGJIsZLyY/FSh2QrnE+fiB7vmWU3XNcb6A==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@testim/chrome-version/-/chrome-version-1.1.4.tgz", + "integrity": "sha512-kIhULpw9TrGYnHp/8VfdcneIcxKnLixmADtukQRtJUmsVlMg0niMkwV0xZmi8hqa57xqilIHjWFA0GKvEjVU5g==", "dev": true }, "@testing-library/dom": { @@ -35514,9 +35568,9 @@ "dev": true }, "axios": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.1.3.tgz", - "integrity": "sha512-00tXVRwKx/FZr/IDVFt4C+f9FYairX517WoGCL6dpOntqLkZofjhu43F/Xl44UOpqa+9sLFDrG/XAnFsUYgkDA==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.1.tgz", + "integrity": "sha512-vfBmhDpKafglh0EldBEbVuoe7DyAavGSLWhuSm5ZSEKQnHhBf0xAAwybbNH1IkrJNGnS/VG4I5yxig1pCEXE4g==", "dev": true, "requires": { "follow-redirects": "^1.15.0", @@ -36712,18 +36766,18 @@ "dev": true }, "chromedriver": { - "version": "107.0.3", - "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-107.0.3.tgz", - "integrity": "sha512-jmzpZgctCRnhYAn0l/NIjP4vYN3L8GFVbterTrRr2Ly3W5rFMb9H8EKGuM5JCViPKSit8FbE718kZTEt3Yvffg==", + "version": "119.0.1", + "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-119.0.1.tgz", + "integrity": "sha512-lpCFFLaXPpvElTaUOWKdP74pFb/sJhWtWqMjn7Ju1YriWn8dT5JBk84BGXMPvZQs70WfCYWecxdMmwfIu1Mupg==", "dev": true, "requires": { - "@testim/chrome-version": "^1.1.3", - "axios": "^1.1.3", - "compare-versions": "^5.0.1", + "@testim/chrome-version": "^1.1.4", + "axios": "^1.6.0", + "compare-versions": "^6.1.0", "extract-zip": "^2.0.1", "https-proxy-agent": "^5.0.1", "proxy-from-env": "^1.1.0", - "tcp-port-used": "^1.0.1" + "tcp-port-used": "^1.0.2" } }, "ci-info": { @@ -37021,9 +37075,9 @@ "dev": true }, "compare-versions": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-5.0.1.tgz", - "integrity": "sha512-v8Au3l0b+Nwkp4G142JcgJFh1/TUhdxut7wzD1Nq1dyp5oa3tXaqb03EXOAB6jS4gMlalkjAUPZBMiAfKUixHQ==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.0.tgz", + "integrity": "sha512-LNZQXhqUvqUTotpZ00qLSaify3b4VFD588aRr8MKFw4CMUr98ytzCW5wDH5qx/DEY5kCDXcbcRuCqL0szEf2tg==", "dev": true }, "component-emitter": { @@ -37387,11 +37441,40 @@ "dev": true }, "cross-fetch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", - "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", + "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", "requires": { - "node-fetch": "2.6.7" + "node-fetch": "^2.6.12" + }, + "dependencies": { + "node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "requires": { + "whatwg-url": "^5.0.0" + } + }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + } } }, "cross-spawn": { @@ -37438,15 +37521,15 @@ "integrity": "sha1-/qJhbcZ2spYmhrOvjb2+GAskTgU=" }, "css-loader": { - "version": "6.7.2", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.2.tgz", - "integrity": "sha512-oqGbbVcBJkm8QwmnNzrFrWTnudnRZC+1eXikLJl0n4ljcfotgRifpg2a1lKy8jTrc4/d9A/ap1GFq1jDKG7J+Q==", + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.8.1.tgz", + "integrity": "sha512-xDAXtEVGlD0gJ07iclwWVkLoZOpEvAWaSyf6W18S2pOC//K8+qUDIx8IIT3D+HjnmkJPQeesOPv5aiUaJsCM2g==", "dev": true, "requires": { "icss-utils": "^5.1.0", - "postcss": "^8.4.18", + "postcss": "^8.4.21", "postcss-modules-extract-imports": "^3.0.0", - "postcss-modules-local-by-default": "^4.0.0", + "postcss-modules-local-by-default": "^4.0.3", "postcss-modules-scope": "^3.0.0", "postcss-modules-values": "^4.0.0", "postcss-value-parser": "^4.2.0", @@ -41226,11 +41309,11 @@ } }, "i18next-http-backend": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-1.4.5.tgz", - "integrity": "sha512-tLuHWuLWl6CmS07o+UB6EcQCaUjrZ1yhdseIN7sfq0u7phsMePJ8pqlGhIAdRDPF/q7ooyo5MID5DRFBCH+x5w==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-2.4.1.tgz", + "integrity": "sha512-CZHzFGDvF8zN7ya1W2lHbgLj2ejPUvPD836+vA3eNXc9eKGUM3MSF6SA2TKBXKBZ2cNG3nxzycCXeM6n/46KWQ==", "requires": { - "cross-fetch": "3.1.5" + "cross-fetch": "4.0.0" } }, "i18next-parser": { @@ -46356,9 +46439,9 @@ "requires": {} }, "postcss-modules-local-by-default": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz", - "integrity": "sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.3.tgz", + "integrity": "sha512-2/u2zraspoACtrbFRnTijMiQtb4GW4BvatjaG/bCjYQo8kLTdevCUlwuBHx2sCnSyrI3x3qj4ZK1j5LQBgzmwA==", "dev": true, "requires": { "icss-utils": "^5.0.0", @@ -47079,11 +47162,11 @@ "requires": {} }, "redux-saga": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/redux-saga/-/redux-saga-1.2.1.tgz", - "integrity": "sha512-fVCicLlf4hLP+KB6H7RHfZlZ8LdYckhaemXBB3wh//a2ESyz/z/l8ygxlm0OqPjS/PARdsQ2hIdAltxEB+NgvA==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/redux-saga/-/redux-saga-1.2.3.tgz", + "integrity": "sha512-HDe0wTR5nhd8Xr5xjGzoyTbdAw6rjy1GDplFt3JKtKN8/MnkQSRqK/n6aQQhpw5NI4ekDVOaW+w4sdxPBaCoTQ==", "requires": { - "@redux-saga/core": "^1.2.1" + "@redux-saga/core": "^1.2.3" } }, "redux-thunk": { @@ -49226,25 +49309,25 @@ "dev": true }, "ts-jest": { - "version": "29.0.3", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.0.3.tgz", - "integrity": "sha512-Ibygvmuyq1qp/z3yTh9QTwVVAbFdDy/+4BtIQR2sp6baF2SJU/8CKK/hhnGIDY2L90Az2jIqTwZPnN2p+BweiQ==", + "version": "29.1.1", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.1.tgz", + "integrity": "sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA==", "dev": true, "requires": { "bs-logger": "0.x", "fast-json-stable-stringify": "2.x", "jest-util": "^29.0.0", - "json5": "^2.2.1", + "json5": "^2.2.3", "lodash.memoize": "4.x", "make-error": "1.x", - "semver": "7.x", + "semver": "^7.5.3", "yargs-parser": "^21.0.1" }, "dependencies": { "semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "requires": { "lru-cache": "^6.0.0" diff --git a/package.json b/package.json index 0e71df484..39e7f24c1 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.6.3", + "version": "1.7.0", "author": "MAKE LLC", "scripts": { "devtools:redux": "redux-devtools --hostname=localhost", @@ -75,7 +75,7 @@ "facepaint": "^1.2.1", "i18next": "^23.5.1", "i18next-browser-languagedetector": "^6.1.5", - "i18next-http-backend": "^1.4.0", + "i18next-http-backend": "^2.4.1", "i18next-parser": "^6.3.0", "lodash.throttle": "4.1.1", "mac-scrollbar": "^0.10.3", @@ -95,14 +95,14 @@ "react-redux": "8.0.5", "react-router-dom": "6.16.0", "redux": "4.2.1", - "redux-saga": "1.2.1", + "redux-saga": "1.2.3", "reselect": "4.1.7", "styled-components": "5.3.6", "typesafe-actions": "5.1.0", "yup": "^0.32.11" }, "devDependencies": { - "@babel/core": "7.20.5", + "@babel/core": "7.23.3", "@babel/plugin-proposal-class-properties": "7.18.6", "@babel/preset-env": "7.23.2", "@babel/preset-react": "7.18.6", @@ -127,10 +127,10 @@ "babel-eslint": "^10.1.0", "babel-loader": "9.1.0", "babel-preset-react-app": "^10.0.0", - "chromedriver": "^107.0.3", + "chromedriver": "^119.0.1", "concurrently": "7.6.0", "copy-webpack-plugin": "^11.0.0", - "css-loader": "6.7.2", + "css-loader": "6.8.1", "css-to-xpath": "^0.1.0", "eslint": "8.49.0", "eslint-config-airbnb": "^19.0.4", @@ -160,7 +160,7 @@ "source-map-loader": "4.0.1", "style-loader": "^3.3.1", "terser-webpack-plugin": "5.3.6", - "ts-jest": "29.0.3", + "ts-jest": "29.1.1", "ts-loader": "9.4.2", "ts-node": "10.9.1", "tsconfig-paths-webpack-plugin": "^4.1.0", diff --git a/src/apps/popup/app-router.tsx b/src/apps/popup/app-router.tsx index 983098e99..10691c271 100644 --- a/src/apps/popup/app-router.tsx +++ b/src/apps/popup/app-router.tsx @@ -37,6 +37,7 @@ import { NftDetailsPage } from '@popup/pages/nft-details'; import { WalletQrCodePage } from '@popup/pages/wallet-qr-code'; import { TransferNftPage } from '@popup/pages/transfer-nft'; import { ChangePasswordPage } from '@popup/pages/change-password'; +import { StakesPage } from '@popup/pages/stakes'; export function AppRouter() { const isLocked = useSelector(selectVaultIsLocked); @@ -252,6 +253,8 @@ function AppRoutes() { path={RouterPath.ChangePassword} element={} /> + } /> + } /> ); } diff --git a/src/apps/popup/pages/activity-details/content.tsx b/src/apps/popup/pages/activity-details/content.tsx index 838fa18d5..619a69303 100644 --- a/src/apps/popup/pages/activity-details/content.tsx +++ b/src/apps/popup/pages/activity-details/content.tsx @@ -41,8 +41,9 @@ import { } from '@libs/ui/utils/formatters'; import { getBlockExplorerContractUrl, - TransferType, - TypeName + ActivityType, + ActivityTypeName, + AuctionManagerEntryPoint } from '@src/constants'; import { selectApiConfigBasedOnActiveNetwork } from '@background/redux/settings/selectors'; @@ -50,7 +51,7 @@ interface ActivityDetailsPageContentProps { fromAccount?: string; toAccount?: string; deployInfo?: ExtendedDeploy | null; - type?: TransferType | null; + type?: ActivityType | null; amount?: string | null; symbol?: string | null; } @@ -80,8 +81,11 @@ const AddressContainer = styled(FlexColumn)` padding: 16px 12px 16px 0; `; -const AmountContainer = styled(AlignedSpaceBetweenFlexRow)` - padding: 8px 16px 8px 0; +const AmountContainer = styled(AlignedSpaceBetweenFlexRow)<{ + emptyAmount?: boolean; +}>` + padding: ${({ emptyAmount }) => + emptyAmount ? '16px 16px 16px 0' : '8px 16px 8px 0'}; `; const RowsContainer = styled(FlexColumn)` @@ -117,7 +121,10 @@ export const ActivityDetailsPageContent = ({ [Erc20EventType.erc20_transfer_from]: t('Transfer from'), [Erc20EventType.erc20_approve]: t('Approve of'), [Erc20EventType.erc20_burn]: t('Burn of'), - [Erc20EventType.erc20_mint]: t('Mint of') + [Erc20EventType.erc20_mint]: t('Mint of'), + [AuctionManagerEntryPoint.delegate]: t('Delegate with'), + [AuctionManagerEntryPoint.undelegate]: t('Undelegate with'), + [AuctionManagerEntryPoint.redelegate]: t('Redelegate with') }; const decimals = deployInfo.contractPackage?.metadata?.decimals; @@ -173,7 +180,7 @@ export const ActivityDetailsPageContent = ({ - {type && {TypeName[type]}} + {type && {ActivityTypeName[type]}} @@ -285,7 +292,11 @@ export const ActivityDetailsPageContent = ({ } /> - contract + + {deployInfo.contractPackage.contract_name === 'Auction' + ? 'System Contract' + : 'contract'} + @@ -295,7 +306,7 @@ export const ActivityDetailsPageContent = ({ )} - + Amount diff --git a/src/apps/popup/pages/home/components/more-buttons-modal/index.tsx b/src/apps/popup/pages/home/components/more-buttons-modal/index.tsx new file mode 100644 index 000000000..5d834395e --- /dev/null +++ b/src/apps/popup/pages/home/components/more-buttons-modal/index.tsx @@ -0,0 +1,49 @@ +import React from 'react'; +import { Trans, useTranslation } from 'react-i18next'; +import styled from 'styled-components'; + +import { Button, Modal, SvgIcon, Typography } from '@libs/ui'; +import { CenteredFlexColumn, SpacingSize } from '@libs/layout'; + +import { ModalButtons } from './modal-buttons'; + +const MoreButton = styled(CenteredFlexColumn)` + cursor: pointer; + + padding: 0 16px; +`; + +interface MoreButtonsModalProps { + handleBuyWithCSPR: () => void; +} + +export const MoreButtonsModal = ({ + handleBuyWithCSPR +}: MoreButtonsModalProps) => { + const { t } = useTranslation(); + + return ( + ( + + )} + children={() => ( + + + + More + + + )} + /> + ); +}; 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 new file mode 100644 index 000000000..4625ca82b --- /dev/null +++ b/src/apps/popup/pages/home/components/more-buttons-modal/modal-buttons.tsx @@ -0,0 +1,119 @@ +import React from 'react'; +import { useSelector } from 'react-redux'; +import { Trans, useTranslation } from 'react-i18next'; +import styled from 'styled-components'; + +import { NetworkSetting } from '@src/constants'; +import { AlignedFlexRow, FlexColumn, SpacingSize } from '@libs/layout'; +import { Button, SvgIcon, Typography } from '@libs/ui'; +import { RouterPath, useTypedNavigate } from '@popup/router'; +import { useCasperToken } from '@src/hooks'; +import { selectActiveNetworkSetting } from '@background/redux/settings/selectors'; + +const ButtonContainer = styled(AlignedFlexRow)` + cursor: pointer; + + padding: 14px 16px; +`; + +interface ButtonsProps { + handleBuyWithCSPR: () => void; +} + +export const ModalButtons = ({ handleBuyWithCSPR }: ButtonsProps) => { + const { t } = useTranslation(); + const navigate = useTypedNavigate(); + const casperToken = useCasperToken(); + + const network = useSelector(selectActiveNetworkSetting); + + return ( + + {network === NetworkSetting.Mainnet && ( + + + + + Buy + + + Buy CSPR with cash + + + + )} + + navigate( + casperToken?.id + ? RouterPath.Transfer.replace( + ':tokenContractPackageHash', + casperToken.id + ).replace( + ':tokenContractHash', + casperToken.contractHash || 'null' + ) + : RouterPath.TransferNoParams + ) + } + > + + + + Send + + + Send CSPR to any account + + + + + navigate(RouterPath.Receive, { + state: { tokenData: casperToken } + }) + } + > + + + + Receive + + + Receive CSPR + + + + navigate(RouterPath.Delegate)} + > + + + Delegate + + + navigate(RouterPath.Undelegate)} + > + + + Undelegate + + + + ); +}; diff --git a/src/apps/popup/pages/home/components/tokens-list/index.tsx b/src/apps/popup/pages/home/components/tokens-list/index.tsx index 6e63d6b93..812cd91f1 100644 --- a/src/apps/popup/pages/home/components/tokens-list/index.tsx +++ b/src/apps/popup/pages/home/components/tokens-list/index.tsx @@ -12,8 +12,8 @@ import { formatErc20TokenBalance } from './utils'; const TotalValueContainer = styled(SpaceBetweenFlexRow)` padding: 12px 16px; - border-top-left-radius: 12px; - border-top-right-radius: 12px; + border-top-left-radius: ${({ theme }) => theme.borderRadius.twelve}px; + border-top-right-radius: ${({ theme }) => theme.borderRadius.twelve}px; background-color: ${({ theme }) => theme.color.backgroundPrimary}; `; @@ -77,7 +77,7 @@ export const TokensList = () => { /> )} marginLeftForItemSeparatorLine={56} - marginLeftForHeaderSeparatorLine={16} + marginLeftForHeaderSeparatorLine={0} /> ); }; diff --git a/src/apps/popup/pages/home/index.tsx b/src/apps/popup/pages/home/index.tsx index 326ae24f7..a8a36f677 100644 --- a/src/apps/popup/pages/home/index.tsx +++ b/src/apps/popup/pages/home/index.tsx @@ -55,6 +55,7 @@ import { import { TokensList } from './components/tokens-list'; import { NftList } from './components/nft-list'; import { DeploysList } from './components/deploys-list'; +import { MoreButtonsModal } from './components/more-buttons-modal'; const DividerLine = styled.hr` margin: 16px 0; @@ -65,11 +66,13 @@ const DividerLine = styled.hr` `; const ButtonsContainer = styled(CenteredFlexRow)` - margin-top: 28px; + margin-top: 16px; `; const ButtonContainer = styled(CenteredFlexColumn)` cursor: pointer; + + padding: 0 16px; `; export function HomePageContent() { @@ -166,6 +169,22 @@ export function HomePageContent() { + {network === NetworkSetting.Mainnet && ( + + + + Buy + + + )} @@ -192,40 +211,7 @@ export function HomePageContent() { Send - - navigate(RouterPath.Receive, { - state: { tokenData: casperToken } - }) - } - > - - - Receive - - - {network === NetworkSetting.Mainnet && ( - - - - Buy - - - )} + diff --git a/src/apps/popup/pages/stakes/amount-step.tsx b/src/apps/popup/pages/stakes/amount-step.tsx new file mode 100644 index 000000000..3b13f19dc --- /dev/null +++ b/src/apps/popup/pages/stakes/amount-step.tsx @@ -0,0 +1,143 @@ +import React, { useEffect, useState } from 'react'; +import { Trans, useTranslation } from 'react-i18next'; +import { UseFormReturn, useWatch } from 'react-hook-form'; +import { useSelector } from 'react-redux'; +import Big from 'big.js'; +import styled from 'styled-components'; + +import { + AlignedFlexRow, + ContentContainer, + ParagraphContainer, + SpacingSize, + VerticalSpaceContainer +} from '@libs/layout'; +import { Error, Input, Typography } from '@libs/ui'; +import { StakeAmountFormValues } from '@libs/ui/forms/stakes-form'; +import { formatFiatAmount, motesToCSPR } from '@libs/ui/utils/formatters'; +import { + selectAccountBalance, + selectAccountCurrencyRate +} from '@background/redux/account-info/selectors'; +import { AuctionManagerEntryPoint, STAKE_COST_MOTES } from '@src/constants'; + +const StakeMaxButton = styled(AlignedFlexRow)` + cursor: pointer; +`; + +interface AmountStepProps { + amountForm: UseFormReturn; + stakesType: AuctionManagerEntryPoint; + stakeAmountMotes: string; + headerText: string; + amountStepText: string; + amountStepMaxAmountValue: string | null; +} + +export const AmountStep = ({ + amountForm, + stakesType, + stakeAmountMotes, + headerText, + amountStepText, + amountStepMaxAmountValue +}: AmountStepProps) => { + const [maxAmountMotes, setMaxAmountMotes] = useState('0'); + + const { t } = useTranslation(); + + const currencyRate = useSelector(selectAccountCurrencyRate); + const csprBalance = useSelector(selectAccountBalance); + + useEffect(() => { + switch (stakesType) { + case AuctionManagerEntryPoint.delegate: { + const maxAmountMotes: string = + csprBalance.amountMotes == null + ? '0' + : Big(csprBalance.amountMotes).sub(STAKE_COST_MOTES).toString(); + + setMaxAmountMotes(maxAmountMotes); + break; + } + case AuctionManagerEntryPoint.undelegate: { + setMaxAmountMotes(stakeAmountMotes); + } + } + }, [csprBalance.amountMotes, stakeAmountMotes, stakesType]); + + const { + register, + formState: { errors }, + control, + setValue, + trigger + } = amountForm; + + const { onChange: onChangeCSPRAmount } = register('amount'); + + const amount = useWatch({ + control, + name: 'amount' + }); + + const amountLabel = t('Amount'); + + const fiatAmount = formatFiatAmount(amount || '0', currencyRate); + + return ( + + + + {headerText} + + + + + { + // replace all non-numeric characters except decimal point + e.target.value = e.target.value.replace(/[^0-9.]/g, ''); + // regex replace decimal point from beginning of string + e.target.value = e.target.value.replace(/^\./, ''); + onChangeCSPRAmount(e); + }} + /> + + + { + setValue('amount', motesToCSPR(maxAmountMotes)); + trigger('amount'); + }} + > + + {amountStepText} + + {amountStepMaxAmountValue && ( + + {amountStepMaxAmountValue} + + )} + + + {errors.amount && ( + + + + )} + + ); +}; diff --git a/src/apps/popup/pages/stakes/confirm-step.tsx b/src/apps/popup/pages/stakes/confirm-step.tsx new file mode 100644 index 000000000..0d2f2afd5 --- /dev/null +++ b/src/apps/popup/pages/stakes/confirm-step.tsx @@ -0,0 +1,134 @@ +import React from 'react'; +import { Trans, useTranslation } from 'react-i18next'; +import styled from 'styled-components'; +import Big from 'big.js'; +import { useSelector } from 'react-redux'; + +import { + ContentContainer, + ParagraphContainer, + SpaceBetweenFlexRow, + SpacingSize, + VerticalSpaceContainer +} from '@libs/layout'; +import { AmountContainer, List, Typography, ValidatorPlate } from '@libs/ui'; +import { + formatFiatAmount, + formatNumber, + motesToCSPR +} from '@libs/ui/utils/formatters'; +import { selectAccountCurrencyRate } from '@background/redux/account-info/selectors'; +import { ValidatorResult } from '@libs/services/validators-service/types'; +import { getAuctionManagerDeployCost } from '@libs/services/deployer-service'; +import { AuctionManagerEntryPoint } from '@src/constants'; + +export const ListItemContainer = styled(SpaceBetweenFlexRow)` + padding: 12px 16px; +`; + +interface ConfirmStepProps { + inputAmountCSPR: string; + validator: ValidatorResult | null; + stakesType: AuctionManagerEntryPoint; + headerText: string; + confirmStepText: string; +} +export const ConfirmStep = ({ + inputAmountCSPR, + validator, + stakesType, + headerText, + confirmStepText +}: ConfirmStepProps) => { + const { t } = useTranslation(); + + const currencyRate = useSelector(selectAccountCurrencyRate); + + const transferFeeMotes = getAuctionManagerDeployCost(stakesType); + + const transferCostInCSPR = formatNumber(motesToCSPR(transferFeeMotes), { + precision: { max: 5 } + }); + const totalCSPR: string = Big(inputAmountCSPR) + .add(transferCostInCSPR) + .toString(); + + const transactionDataRows = [ + { + id: 1, + text: confirmStepText, + amount: formatNumber(inputAmountCSPR, { + precision: { max: 5 } + }), + fiatPrice: formatFiatAmount(inputAmountCSPR, currencyRate), + symbol: 'CSPR' + }, + { + id: 2, + text: t('Transaction fee'), + amount: transferCostInCSPR, + fiatPrice: formatFiatAmount(transferCostInCSPR, currencyRate, 3), + symbol: 'CSPR' + }, + { + id: 3, + text: t('Total'), + amount: formatNumber(totalCSPR, { + precision: { max: 5 } + }), + fiatPrice: formatFiatAmount(totalCSPR, currencyRate), + symbol: 'CSPR', + bold: true + } + ]; + + const validatorLabel = t('To validator'); + + if (!validator) { + return null; + } + + return ( + + + + {headerText} + + + + + + + {t('Amount and fee')} + + ( + + + {listItems.text} + + + {`${listItems.amount} ${listItems.symbol}`} + + {listItems.fiatPrice == null + ? null + : listItems.fiatPrice || 'Not available'} + + + + )} + marginLeftForItemSeparatorLine={8} + /> + + ); +}; diff --git a/src/apps/popup/pages/stakes/content.tsx b/src/apps/popup/pages/stakes/content.tsx new file mode 100644 index 000000000..0d9b0a57b --- /dev/null +++ b/src/apps/popup/pages/stakes/content.tsx @@ -0,0 +1,131 @@ +import React, { useEffect, useState } from 'react'; +import { UseFormReturn } from 'react-hook-form'; + +import { ValidatorStep } from '@popup/pages/stakes/validator-step'; +import { AmountStep } from '@popup/pages/stakes/amount-step'; +import { + StakeAmountFormValues, + StakeValidatorFormValues +} from '@libs/ui/forms/stakes-form'; +import { ConfirmStep } from '@popup/pages/stakes/confirm-step'; +import { TransferSuccessScreen, ValidatorDropdownInput } from '@libs/ui'; +import { AuctionManagerEntryPoint, StakeSteps } from '@src/constants'; +import { ValidatorResultWithId } from '@libs/services/validators-service/types'; +import { formatNumber, motesToCSPR } from '@libs/ui/utils/formatters'; + +interface DelegateStakePageContentProps { + stakeStep: StakeSteps; + validatorForm: UseFormReturn; + amountForm: UseFormReturn; + inputAmountCSPR: string; + validator: ValidatorResultWithId | null; + setValidator: React.Dispatch< + React.SetStateAction + >; + stakesType: AuctionManagerEntryPoint; + stakeAmountMotes: string; + setStakeAmount: React.Dispatch>; + validatorList: ValidatorResultWithId[] | null; +} + +export const StakesPageContent = ({ + stakeStep, + validatorForm, + amountForm, + inputAmountCSPR, + validator, + setValidator, + stakesType, + stakeAmountMotes, + setStakeAmount, + validatorList +}: DelegateStakePageContentProps) => { + const [validateStepHeaderText, setValidateStepHeaderText] = useState(''); + const [amountStepHeaderText, setAmountStepHeaderText] = useState(''); + const [confirmStepHeaderText, setConfirmStepHeaderText] = useState(''); + const [successStepHeaderText, setSuccessStepHeaderText] = useState(''); + const [confirmStepText, setConfirmStepText] = useState(''); + const [amountStepText, setAmountStepText] = useState(''); + const [amountStepMaxAmountValue, setAmountStepMaxAmountValue] = useState< + string | null + >(null); + + useEffect(() => { + const formattedAmountCSPR = + stakeAmountMotes && + formatNumber(motesToCSPR(stakeAmountMotes), { precision: { max: 4 } }); + + switch (stakesType) { + case AuctionManagerEntryPoint.delegate: { + setValidateStepHeaderText('Delegate'); + setAmountStepHeaderText('Delegate amount'); + setConfirmStepHeaderText('Confirm delegation'); + setSuccessStepHeaderText('You’ve submitted a delegation'); + + setAmountStepText('Delegate max'); + setConfirmStepText('You’ll delegate'); + break; + } + case AuctionManagerEntryPoint.undelegate: { + setValidateStepHeaderText('Undelegate'); + setAmountStepHeaderText('Undelegate amount'); + setConfirmStepHeaderText('Confirm undelegation'); + setSuccessStepHeaderText('You’ve submitted an undelegation'); + + setAmountStepText('Undelegate max:'); + setAmountStepMaxAmountValue(`${formattedAmountCSPR} CSPR`); + setConfirmStepText('You’ll undelegate'); + break; + } + + default: + throw Error('fetch validator: unknown stakes type'); + } + }, [stakeAmountMotes, stakesType]); + + switch (stakeStep) { + case StakeSteps.Validator: { + return ( + + + + ); + } + case StakeSteps.Amount: { + return ( + + ); + } + case StakeSteps.Confirm: { + return ( + + ); + } + case StakeSteps.Success: { + return ; + } + default: { + throw Error('Out of bound: StakeSteps'); + } + } +}; diff --git a/src/apps/popup/pages/stakes/index.tsx b/src/apps/popup/pages/stakes/index.tsx new file mode 100644 index 000000000..0eaae0a29 --- /dev/null +++ b/src/apps/popup/pages/stakes/index.tsx @@ -0,0 +1,412 @@ +import React, { useEffect, useState } from 'react'; +import { Trans, useTranslation } from 'react-i18next'; +import { useSelector } from 'react-redux'; + +import { + CenteredFlexRow, + ContentContainer, + FooterButtonsContainer, + HeaderSubmenuBarNavLink, + ParagraphContainer, + PopupHeader, + PopupLayout, + SpaceBetweenFlexRow, + SpacingSize +} from '@libs/layout'; +import { StakesPageContent } from '@popup/pages/stakes/content'; +import { Button, HomePageTabsId, Typography } from '@libs/ui'; +import { selectVaultActiveAccount } from '@background/redux/vault/selectors'; +import { createAsymmetricKey } from '@libs/crypto/create-asymmetric-key'; +import { selectApiConfigBasedOnActiveNetwork } from '@background/redux/settings/selectors'; +import { + AuctionManagerEntryPoint, + STAKE_COST_MOTES, + StakeSteps +} from '@src/constants'; +import { dispatchToMainStore } from '@background/redux/utils'; +import { accountPendingTransactionsChanged } from '@background/redux/account-info/actions'; +import { dispatchFetchExtendedDeploysInfo } from '@libs/services/account-activity-service'; +import { makeAuctionManagerDeploy } from '@libs/services/deployer-service'; +import { RouterPath, useTypedLocation, useTypedNavigate } from '@popup/router'; +import { useStakesForm } from '@libs/ui/forms/stakes-form'; +import { selectAccountBalance } from '@background/redux/account-info/selectors'; +import { calculateSubmitButtonDisabled } from '@libs/ui/forms/get-submit-button-state-from-validation'; +import { + CSPRtoMotes, + formatNumber, + motesToCSPR +} from '@libs/ui/utils/formatters'; +import { ValidatorResultWithId } from '@libs/services/validators-service/types'; +import { + dispatchFetchAuctionValidatorsRequest, + dispatchFetchValidatorsDetailsDataRequest +} from '@libs/services/validators-service'; +import { NoDelegations } from '@popup/pages/stakes/no-delegations'; + +export const StakesPage = () => { + const [stakeStep, setStakeStep] = useState(StakeSteps.Validator); + const [validatorPublicKey, setValidatorPublicKey] = useState(''); + const [inputAmountCSPR, setInputAmountCSPR] = useState(''); + const [isSubmitButtonDisable, setIsSubmitButtonDisable] = useState(true); + const [validator, setValidator] = useState( + null + ); + const [stakesType, setStakesType] = useState( + AuctionManagerEntryPoint.delegate + ); + const [stakeAmountMotes, setStakeAmountMotes] = useState(''); + const [validatorList, setValidatorList] = useState< + ValidatorResultWithId[] | null + >(null); + const [loading, setLoading] = useState(true); + + const activeAccount = useSelector(selectVaultActiveAccount); + const { networkName, nodeUrl, auctionManagerContractHash, casperApiUrl } = + useSelector(selectApiConfigBasedOnActiveNetwork); + const csprBalance = useSelector(selectAccountBalance); + + 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 + })); + + setValidatorList(validatorListWithId); + } + }) + .finally(() => { + setLoading(false); + }); + } + } + }, [activeAccount, pathname, casperApiUrl]); + + const { amountForm, validatorForm } = useStakesForm( + csprBalance.amountMotes, + stakesType, + stakeAmountMotes, + validator?.delegators_number + ); + const { formState: amountFormState, getValues: getValuesAmountForm } = + amountForm; + const { formState: validatorFormState, getValues: getValuesValidatorForm } = + validatorForm; + + // 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 submitStake = () => { + if (activeAccount) { + const motesAmount = CSPRtoMotes(inputAmountCSPR); + + const KEYS = createAsymmetricKey( + activeAccount.publicKey, + activeAccount.secretKey + ); + + const deploy = makeAuctionManagerDeploy( + stakesType, + activeAccount.publicKey, + validatorPublicKey, + null, + motesAmount, + networkName, + auctionManagerContractHash + ); + + const signDeploy = deploy.sign([KEYS]); + + signDeploy.send(nodeUrl).then((deployHash: string) => { + if (deployHash) { + let triesLeft = 10; + const interval = setInterval(async () => { + const { payload: extendedDeployInfo } = + await dispatchFetchExtendedDeploysInfo(deployHash); + if (extendedDeployInfo) { + dispatchToMainStore( + accountPendingTransactionsChanged(extendedDeployInfo) + ); + clearInterval(interval); + } else if (triesLeft === 0) { + clearInterval(interval); + } + + triesLeft--; + // Note: this timeout is needed because the deploy is not immediately visible in the explorer + }, 2000); + } + }); + // TODO: need UI in case when the delegation request is failed + setStakeStep(StakeSteps.Success); + } + }; + + const getButtonProps = () => { + const isValidatorFormButtonDisabled = calculateSubmitButtonDisabled({ + isValid: validatorFormState.isValid + }); + const isAmountFormButtonDisabled = calculateSubmitButtonDisabled({ + isValid: amountFormState.isValid + }); + + switch (stakeStep) { + case StakeSteps.Validator: { + return { + disabled: isValidatorFormButtonDisabled, + onClick: () => { + const { validatorPublicKey } = getValuesValidatorForm(); + + setStakeStep(StakeSteps.Amount); + setValidatorPublicKey(validatorPublicKey); + } + }; + } + case StakeSteps.Amount: { + return { + disabled: isAmountFormButtonDisabled, + onClick: () => { + const { amount: _amount } = getValuesAmountForm(); + + setInputAmountCSPR(_amount); + setStakeStep(StakeSteps.Confirm); + } + }; + } + case StakeSteps.Confirm: { + return { + disabled: + isSubmitButtonDisable || + isValidatorFormButtonDisabled || + isAmountFormButtonDisabled, + onClick: submitStake + }; + } + case StakeSteps.Success: { + return { + onClick: () => { + navigate(RouterPath.Home, { + state: { + // set the active tab to deploys + activeTabId: HomePageTabsId.Deploys + } + }); + } + }; + } + } + }; + + const handleBackButton = () => { + switch (stakeStep) { + case StakeSteps.Validator: { + navigate(-1); + break; + } + case StakeSteps.Amount: { + setStakeStep(StakeSteps.Validator); + break; + } + case StakeSteps.Confirm: { + setStakeStep(StakeSteps.Amount); + break; + } + + default: { + navigate(-1); + break; + } + } + }; + + const getConfirmButtonText = () => { + switch (stakesType) { + case AuctionManagerEntryPoint.delegate: { + return t('Confirm delegation'); + } + case AuctionManagerEntryPoint.undelegate: { + return t('Confirm undelegation'); + } + default: { + return t('Confirm'); + } + } + }; + + if (loading) { + return ( + ( + + )} + renderContent={() => ( + + + + Loading... + + + + )} + /> + ); + } + + if ( + stakesType === AuctionManagerEntryPoint.undelegate && + validatorList !== null && + validatorList.length === 0 + ) { + return ( + ( + + )} + renderContent={() => } + renderFooter={() => ( + + + + )} + /> + ); + } + + return ( + ( + ( + + ) + } + /> + )} + renderContent={() => ( + + )} + renderFooter={() => ( + + {stakeStep === StakeSteps.Amount ? ( + + + Transaction fee + + + {formatNumber(motesToCSPR(STAKE_COST_MOTES), { + precision: { max: 5 } + })}{' '} + CSPR + + + ) : null} + + + )} + /> + ); +}; diff --git a/src/apps/popup/pages/stakes/no-delegations.tsx b/src/apps/popup/pages/stakes/no-delegations.tsx new file mode 100644 index 000000000..1d74464e3 --- /dev/null +++ b/src/apps/popup/pages/stakes/no-delegations.tsx @@ -0,0 +1,34 @@ +import React from 'react'; +import { Trans, useTranslation } from 'react-i18next'; + +import { + ContentContainer, + ParagraphContainer, + SpacingSize +} from '@libs/layout'; +import { SvgIcon, Typography } from '@libs/ui'; + +export const NoDelegations = () => { + const { t } = useTranslation(); + + return ( + + + + + + + You haven’t delegated with this account yet + + + + + + You can only undelegate if you’ve delegated from this account + before. + + + + + ); +}; diff --git a/src/apps/popup/pages/stakes/validator-step.tsx b/src/apps/popup/pages/stakes/validator-step.tsx new file mode 100644 index 000000000..c6cae9dc9 --- /dev/null +++ b/src/apps/popup/pages/stakes/validator-step.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import { Trans, useTranslation } from 'react-i18next'; + +import { + ContentContainer, + ParagraphContainer, + SpacingSize +} from '@libs/layout'; +import { Typography } from '@libs/ui'; + +interface ValidatorStepProps { + children?: React.ReactNode; + headerText: string; +} +export const ValidatorStep = ({ headerText, children }: ValidatorStepProps) => { + const { t } = useTranslation(); + + return ( + + + + {headerText} + + + + {children} + + ); +}; diff --git a/src/apps/popup/pages/transfer-nft/index.tsx b/src/apps/popup/pages/transfer-nft/index.tsx index 75b55e3bc..4304bef96 100644 --- a/src/apps/popup/pages/transfer-nft/index.tsx +++ b/src/apps/popup/pages/transfer-nft/index.tsx @@ -176,7 +176,7 @@ export const TransferNftPage = () => { )} renderContent={() => showSuccessScreen ? ( - + ) : ( ; + return ; } default: { diff --git a/src/apps/popup/pages/transfer/index.tsx b/src/apps/popup/pages/transfer/index.tsx index 4f4d7e6ef..306ff1a3d 100644 --- a/src/apps/popup/pages/transfer/index.tsx +++ b/src/apps/popup/pages/transfer/index.tsx @@ -117,9 +117,7 @@ export const TransferPage = () => { // event listener for enable/disable submit button useEffect(() => { - const layoutContentContainer = document.querySelector( - '#layout-content-container' - ); + const layoutContentContainer = document.querySelector('#ms-container'); // if the content is not scrollable, we can enable the submit button if ( diff --git a/src/apps/popup/router/paths.ts b/src/apps/popup/router/paths.ts index cc2e6eba6..3922bc54c 100644 --- a/src/apps/popup/router/paths.ts +++ b/src/apps/popup/router/paths.ts @@ -22,5 +22,7 @@ export enum RouterPath { NftDetails = '/nft-details/:contractPackageHash/nfts/:tokenId', GenerateWalletQRCode = '/generate-wallet-qr-code', TransferNft = '/transfer-nft/:contractPackageHash/nfts/:tokenId', - ChangePassword = '/change-password' + ChangePassword = '/change-password', + Delegate = '/delegate', + Undelegate = '/undelegate' } diff --git a/src/apps/popup/router/types.ts b/src/apps/popup/router/types.ts index 69c14e73c..1156cf0f5 100644 --- a/src/apps/popup/router/types.ts +++ b/src/apps/popup/router/types.ts @@ -1,4 +1,4 @@ -import { TransferType } from '@src/constants'; +import { ActivityType } from '@src/constants'; import { TokenType } from '@src/hooks'; export type LocationState = { @@ -7,7 +7,7 @@ export type LocationState = { fromAccount: string; toAccount: string; deployHash: string; - type: TransferType | null; + type: ActivityType | null; amount?: string; symbol?: string; isDeploysList?: boolean; diff --git a/src/assets/icons/burn.svg b/src/assets/icons/burn.svg new file mode 100644 index 000000000..be330d02b --- /dev/null +++ b/src/assets/icons/burn.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/delegate.svg b/src/assets/icons/delegate.svg new file mode 100644 index 000000000..87128f7de --- /dev/null +++ b/src/assets/icons/delegate.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/empty-state.svg b/src/assets/icons/empty-state.svg new file mode 100644 index 000000000..9cb85889b --- /dev/null +++ b/src/assets/icons/empty-state.svg @@ -0,0 +1,739 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/icons/undelegate.svg b/src/assets/icons/undelegate.svg new file mode 100644 index 000000000..9904c3ace --- /dev/null +++ b/src/assets/icons/undelegate.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/background/index.ts b/src/background/index.ts index c724a0560..5c7fc75a6 100644 --- a/src/background/index.ts +++ b/src/background/index.ts @@ -132,6 +132,10 @@ import { } from '@background/redux/account-info/actions'; import { fetchErc20TokenActivity } from '@src/libs/services/account-activity-service/erc20-token-activity-service'; import { fetchNftTokens } from '@libs/services/nft-service'; +import { + fetchAuctionValidators, + fetchValidatorsDetailsData +} from '@libs/services/validators-service'; // setup default onboarding action async function handleActionClick() { @@ -748,6 +752,45 @@ browser.runtime.onMessage.addListener( return; } + case getType(serviceMessage.fetchAuctionValidatorsRequest): { + const { casperApiUrl } = selectApiConfigBasedOnActiveNetwork( + store.getState() + ); + + try { + const data = await fetchAuctionValidators({ casperApiUrl }); + + return sendResponse( + serviceMessage.fetchAuctionValidatorsResponse(data) + ); + } catch (error) { + console.error(error); + } + + return; + } + + case getType(serviceMessage.fetchValidatorsDetailsDataRequest): { + const { casperApiUrl } = selectApiConfigBasedOnActiveNetwork( + store.getState() + ); + + try { + const data = await fetchValidatorsDetailsData({ + casperApiUrl, + publicKey: action.payload.publicKey + }); + + return sendResponse( + serviceMessage.fetchValidatorsDetailsDataResponse(data) + ); + } catch (error) { + console.error(error); + } + + return; + } + // TODO: All below should be removed when Import Account is integrated with window case 'check-secret-key-exist' as any: { const { secretKeyBase64 } = ( diff --git a/src/background/redux/settings/selectors.ts b/src/background/redux/settings/selectors.ts index 62d11254a..4b9cf50d9 100644 --- a/src/background/redux/settings/selectors.ts +++ b/src/background/redux/settings/selectors.ts @@ -2,6 +2,7 @@ import { RootState } from 'typesafe-actions'; import { createSelector } from 'reselect'; import { + AuctionManagerContractHash, CasperApiUrl, CasperLiveUrl, CasperNodeUrl, @@ -24,14 +25,16 @@ export const selectApiConfigBasedOnActiveNetwork = createSelector( casperLiveUrl: CasperLiveUrl.MainnetUrl, casperApiUrl: CasperApiUrl.MainnetUrl, networkName: NetworkName.Mainnet, - nodeUrl: CasperNodeUrl.MainnetUrl + nodeUrl: CasperNodeUrl.MainnetUrl, + auctionManagerContractHash: AuctionManagerContractHash.Mainnet }; case NetworkSetting.Testnet: return { casperLiveUrl: CasperLiveUrl.TestnetUrl, casperApiUrl: CasperApiUrl.TestnetUrl, networkName: NetworkName.Testnet, - nodeUrl: CasperNodeUrl.TestnetUrl + nodeUrl: CasperNodeUrl.TestnetUrl, + auctionManagerContractHash: AuctionManagerContractHash.Testnet }; default: throw new Error(`Unknown network: ${activeNetwork}`); diff --git a/src/background/service-message.ts b/src/background/service-message.ts index 8cffd7e6d..2885da328 100644 --- a/src/background/service-message.ts +++ b/src/background/service-message.ts @@ -10,6 +10,10 @@ import { import { ErrorResponse, PaginatedResponse } from '@libs/services/types'; import { ContractPackageWithBalance } from '@libs/services/erc20-service'; import { NFTTokenResult } from '@libs/services/nft-service'; +import { + DelegatorResult, + ValidatorResult +} from '@libs/services/validators-service'; type Meta = void; @@ -80,7 +84,19 @@ export const serviceMessage = { fetchNftTokensResponse: createAction('FETCH_NFT_TOKENS_RESPONSE')< PaginatedResponse | ErrorResponse, Meta - >() + >(), + fetchAuctionValidatorsRequest: createAction( + 'FETCH_AUCTION_VALIDATORS' + )(), + fetchAuctionValidatorsResponse: createAction( + 'FETCH_AUCTION_VALIDATORS_RESPONSE' + ) | ErrorResponse, Meta>(), + fetchValidatorsDetailsDataRequest: createAction( + 'FETCH_VALIDATORS_DETAILS_DATA' + )<{ publicKey: string }, Meta>(), + fetchValidatorsDetailsDataResponse: createAction( + 'FETCH_VALIDATORS_DETAILS_DATA_RESPONSE' + ) | ErrorResponse, Meta>() }; export type ServiceMessage = ActionType; diff --git a/src/constants.ts b/src/constants.ts index 0ca8ec244..c53c13976 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -12,6 +12,7 @@ export const NFT_TOKENS_REFRESH_RATE = 60 * SECOND; export const ACCOUNT_DEPLOY_REFRESH_RATE = 30 * SECOND; export const ACCOUNT_CASPER_ACTIVITY_REFRESH_RATE = 30 * SECOND; export const ERC20_TOKEN_ACTIVITY_REFRESH_RATE = 30 * SECOND; +export const VALIDATORS_REFRESH_RATE = 30 * SECOND; export const LOGIN_RETRY_ATTEMPTS_LIMIT = 5; @@ -20,6 +21,9 @@ export const TRANSFER_MIN_AMOUNT_MOTES = '2500000000'; // 2.5 CSPR export const ERC20_PAYMENT_AMOUNT_AVERAGE_MOTES = '1500000000'; // 1.5 CSPR export const NFT_CEP47_PAYMENT_AMOUNT_AVERAGE_MOTES = '1000000000'; // 1 CSPR export const NFT_CEP78_PAYMENT_AMOUNT_AVERAGE_MOTES = '3000000000'; // 3 CSPR +export const STAKE_COST_MOTES = '2500000000'; // 2.5 CSPR +export const DELEGATION_MIN_AMOUNT_MOTES = '500000000000'; // 500 CSPR +export const MAX_DELEGATORS = 1200; export const getBlockExplorerAccountUrl = (baseUrl: string, hash: string) => `${baseUrl}/account/${hash}`; @@ -77,34 +81,64 @@ export enum NetworkName { Testnet = 'casper-test' } -export enum TransferType { +export enum AuctionManagerContractHash { + Mainnet = 'ccb576d6ce6dec84a551e48f0d0b7af89ddba44c7390b690036257a04a3ae9ea', + Testnet = '93d923e336b20a4c4ca14d592b60e5bd3fe330775618290104f9beb326db7ae2' +} + +export enum ActivityType { Sent = 'Sent', Received = 'Received', - Unknown = 'Unknown' + Unknown = 'Unknown', + Delegated = 'Delegated', + Undelegated = 'Undelegated', + Redelegated = 'Redelegated', + Mint = 'Mint', + Burn = 'Burn' } -export const ShortTypeName = { - [TransferType.Sent]: 'Sent', - [TransferType.Received]: 'Recv', - [TransferType.Unknown]: 'Unk' +export const ActivityShortTypeName = { + [ActivityType.Sent]: 'Sent', + [ActivityType.Received]: 'Recv', + [ActivityType.Unknown]: 'Unk', + [ActivityType.Delegated]: 'Deleg', + [ActivityType.Undelegated]: 'Undeleg', + [ActivityType.Redelegated]: 'Redeleg', + [ActivityType.Mint]: 'Mint', + [ActivityType.Burn]: 'Burn' }; -export const TypeName = { - [TransferType.Sent]: 'Sent', - [TransferType.Received]: 'Received', - [TransferType.Unknown]: 'Unknown' +export const ActivityTypeName = { + [ActivityType.Sent]: 'Sent', + [ActivityType.Received]: 'Received', + [ActivityType.Unknown]: 'Unknown', + [ActivityType.Delegated]: 'Delegated', + [ActivityType.Undelegated]: 'Undelegated', + [ActivityType.Redelegated]: 'Redelegated', + [ActivityType.Mint]: 'Mint', + [ActivityType.Burn]: 'Burn' }; -export const TypeIcons = { - [TransferType.Sent]: 'assets/icons/transfer.svg', - [TransferType.Received]: 'assets/icons/receive.svg', - [TransferType.Unknown]: 'assets/icons/info.svg' +export const ActivityTypeIcons = { + [ActivityType.Sent]: 'assets/icons/transfer.svg', + [ActivityType.Received]: 'assets/icons/receive.svg', + [ActivityType.Unknown]: 'assets/icons/info.svg', + [ActivityType.Delegated]: 'assets/icons/delegate.svg', + [ActivityType.Undelegated]: 'assets/icons/undelegate.svg', + [ActivityType.Redelegated]: 'assets/icons/undelegate.svg', + [ActivityType.Mint]: 'assets/icons/info.svg', + [ActivityType.Burn]: 'assets/icons/burn.svg' }; -export const TypeColors = { - [TransferType.Sent]: 'contentAction', - [TransferType.Received]: 'contentPositive', - [TransferType.Unknown]: 'contentDisabled' +export const ActivityTypeColors = { + [ActivityType.Sent]: 'contentAction', + [ActivityType.Received]: 'contentPositive', + [ActivityType.Unknown]: 'contentDisabled', + [ActivityType.Delegated]: 'contentAction', + [ActivityType.Undelegated]: 'contentAction', + [ActivityType.Redelegated]: 'contentAction', + [ActivityType.Mint]: 'contentDisabled', + [ActivityType.Burn]: 'contentAction' }; export enum HomePageTabName { @@ -112,3 +146,21 @@ export enum HomePageTabName { Deploys = 'Deploys', NFTs = 'NFTs' } + +export enum StakeSteps { + Validator = 'validator', + Amount = 'amount', + Confirm = 'confirm', + Success = 'success' +} + +export enum AuctionManagerEntryPoint { + delegate = 'delegate', + undelegate = 'undelegate', + redelegate = 'redelegate' +} + +export enum TokenEntryPoint { + mint = 'mint', + burn = 'burn' +} diff --git a/src/libs/layout/header/header-connection-status.tsx b/src/libs/layout/header/header-connection-status.tsx index 9afefabb1..1e18985aa 100644 --- a/src/libs/layout/header/header-connection-status.tsx +++ b/src/libs/layout/header/header-connection-status.tsx @@ -1,38 +1,43 @@ import React from 'react'; import styled from 'styled-components'; import { useSelector } from 'react-redux'; -import { useTranslation } from 'react-i18next'; -import { AccountList, Modal, SvgIcon, Typography } from '@libs/ui'; +import { AccountList, Hash, HashVariant, Modal, SvgIcon } from '@libs/ui'; import { AlignedFlexRow, SpacingSize } from '@libs/layout'; -import { selectCountOfConnectedAccountsWithActiveOrigin } from '@src/background/redux/vault/selectors'; +import { selectVaultActiveAccount } from '@src/background/redux/vault/selectors'; const ConnectionStatusContainer = styled(AlignedFlexRow)` width: fit-content; background-color: rgb(0, 0, 0, 0.16); - padding: 4px 8px; - border-radius: ${({ theme }) => theme.borderRadius.hundred}px; + padding: 6px 8px 6px 14px; + border-top-right-radius: ${({ theme }) => theme.borderRadius.hundred}px; + border-bottom-right-radius: ${({ theme }) => theme.borderRadius.hundred}px; + + position: relative; + left: -2px; `; export function HeaderConnectionStatus() { - const { t } = useTranslation(); - const countOfConnectedAccounts = useSelector( - selectCountOfConnectedAccountsWithActiveOrigin - ); + const activeAccount = useSelector(selectVaultActiveAccount); return ( ( )} children={({ isOpen }) => ( - - {countOfConnectedAccounts > 0 - ? `${countOfConnectedAccounts} ${t('Connected')}` - : t('Disconnected')} - + hexToRGBA(theme.color.black, '0.16')}; - padding: 4px 8px; + padding: 6px 8px; border-radius: ${({ theme }) => theme.borderRadius.hundred}px; `; @@ -50,6 +50,7 @@ export const HeaderNetworkSwitcher = () => { return ( ( void; + backTypeWithBalance?: boolean; } export function HeaderSubmenuBarNavLink({ linkType, - onClick + onClick, + backTypeWithBalance }: HeaderSubmenuBarNavLinkProps) { const { t } = useTranslation(); const navigate = useTypedNavigate(); + const balance = useSelector(selectAccountBalance); + + const formattedBalance = formatNumber( + (balance.amountMotes && motesToCSPR(balance.amountMotes)) || '' + ); + switch (linkType) { case 'close': return ( @@ -52,7 +64,29 @@ export function HeaderSubmenuBarNavLink({ ); case 'back': - return ( + return backTypeWithBalance ? ( + <> + { + if (onClick) { + onClick(); + } else { + navigate(-1); + } + }} + withLeftChevronIcon + /> + + + Balance: + + + {`${formattedBalance} CSPR`} + + + + ) : ( { diff --git a/src/libs/layout/header/index.tsx b/src/libs/layout/header/index.tsx index 3b2ad3a7a..5db9269d9 100644 --- a/src/libs/layout/header/index.tsx +++ b/src/libs/layout/header/index.tsx @@ -3,13 +3,12 @@ import styled from 'styled-components'; import { useSelector } from 'react-redux'; import { + AlignedFlexRow, AlignedSpaceBetweenFlexRow, - FlexRow, HeaderContainer, LeftAlignedCenteredFlexRow, Logo, - LogoContainer, - SpacingSize + LogoContainer } from '@src/libs/layout'; import { Avatar, SvgIcon } from '@libs/ui'; import { @@ -68,7 +67,7 @@ export function PopupHeader({ <> {withConnectionStatus && activeAccount?.publicKey ? ( - + - + ) : ( <> diff --git a/src/libs/services/account-activity-service/types.ts b/src/libs/services/account-activity-service/types.ts index 726bb1719..8ff28f6db 100644 --- a/src/libs/services/account-activity-service/types.ts +++ b/src/libs/services/account-activity-service/types.ts @@ -52,6 +52,7 @@ export type ExtendedDeployArgsResult = { to?: ExtendedDeployClTypeResult; validator?: ExtendedDeployClTypeResult; new_validator?: ExtendedDeployClTypeResult; + delegator?: ExtendedDeployClTypeResult; }; export interface ExtendedDeployResult { diff --git a/src/libs/services/deployer-service/index.ts b/src/libs/services/deployer-service/index.ts index 6f77f274d..5e7f648fe 100644 --- a/src/libs/services/deployer-service/index.ts +++ b/src/libs/services/deployer-service/index.ts @@ -1,11 +1,35 @@ -import { CasperServiceByJsonRPC, CLPublicKey, DeployUtil } from 'casper-js-sdk'; +import { + CasperServiceByJsonRPC, + CLPublicKey, + CLValueBuilder, + decodeBase16, + DeployUtil, + RuntimeArgs +} from 'casper-js-sdk'; +import { sub } from 'date-fns'; import { signDeploy } from '@libs/crypto'; +import { getRawPublicKey } from '@libs/entities/Account'; +import { AuctionManagerEntryPoint, STAKE_COST_MOTES } from '@src/constants'; import { RPCResponse } from './types'; const casperService = (url: string) => new CasperServiceByJsonRPC(url); +export const getAuctionManagerDeployCost = ( + entryPoint: AuctionManagerEntryPoint +) => { + switch (entryPoint) { + case AuctionManagerEntryPoint.delegate: + case AuctionManagerEntryPoint.undelegate: + case AuctionManagerEntryPoint.redelegate: + return STAKE_COST_MOTES; + + default: + throw Error('getAuctionManagerDeployCost: unknown entry point'); + } +}; + export const signAndDeploy = ( deploy: DeployUtil.Deploy, senderPublicKeyHex: string, @@ -33,3 +57,51 @@ export const signAndDeploy = ( throw error; }); }; + +export const makeAuctionManagerDeploy = ( + contractEntryPoint: AuctionManagerEntryPoint, + delegatorPublicKeyHex: string, + validatorPublicKeyHex: string, + redelegateValidatorPublicKeyHex: string | null, + amountMotes: string, + networkName: string, + auctionManagerContractHash: string +) => { + const hash = decodeBase16(auctionManagerContractHash); + + const delegatorPublicKey = getRawPublicKey(delegatorPublicKeyHex); + const validatorPublicKey = getRawPublicKey(validatorPublicKeyHex); + const newValidatorPublicKey = + redelegateValidatorPublicKeyHex && + getRawPublicKey(redelegateValidatorPublicKeyHex); + + const runtimeArgs = RuntimeArgs.fromMap({ + validator: validatorPublicKey, + delegator: delegatorPublicKey, + amount: CLValueBuilder.u512(amountMotes), + ...(newValidatorPublicKey && { + new_validator: newValidatorPublicKey + }) + }); + + const deployParams = new DeployUtil.DeployParams( + delegatorPublicKey, + networkName, + undefined, + undefined, + undefined, + sub(new Date(), { seconds: 2 }).getTime() + ); // https://github.com/casper-network/casper-node/issues/4152 + + const session = DeployUtil.ExecutableDeployItem.newStoredContractByHash( + hash, + contractEntryPoint, + runtimeArgs + ); + + const deployCost = getAuctionManagerDeployCost(contractEntryPoint); + + const payment = DeployUtil.standardPayment(deployCost); + + return DeployUtil.makeDeploy(deployParams, session, payment); +}; diff --git a/src/libs/services/validators-service/constants.ts b/src/libs/services/validators-service/constants.ts new file mode 100644 index 000000000..9a2b4e886 --- /dev/null +++ b/src/libs/services/validators-service/constants.ts @@ -0,0 +1,9 @@ +export const getAuctionValidatorsUrl = (casperApiUrl: string) => + `${casperApiUrl}/auction-validators?page=1&limit=-1&fields=account_info,average_performance&is_active=true`; + +export const getValidatorsDetailsDataUrl = ( + casperApiUrl: string, + publicKey: string +) => ` + ${casperApiUrl}/accounts/${publicKey}/delegations?page=1&limit=100&fields=validator,validator_account_info +`; diff --git a/src/libs/services/validators-service/index.ts b/src/libs/services/validators-service/index.ts new file mode 100644 index 000000000..963aa288f --- /dev/null +++ b/src/libs/services/validators-service/index.ts @@ -0,0 +1,3 @@ +export * from './types'; +export * from './validators-service'; +export * from './constants'; diff --git a/src/libs/services/validators-service/types.ts b/src/libs/services/validators-service/types.ts new file mode 100644 index 000000000..486a4c5b0 --- /dev/null +++ b/src/libs/services/validators-service/types.ts @@ -0,0 +1,124 @@ +export interface ValidatorResult { + fee: number; + is_active: boolean; + self_stake: string; + bid_amount: string; + total_stake: string; + self_share: string; + public_key: string; + network_share: string; + era_id: number; + delegators_number: number; + delegator_stake: string; + rank: number; + account_info?: AccountInfoResult; + average_performance?: ValidatorAveragePerformanceResult; +} + +export interface ValidatorResultWithId extends ValidatorResult { + id: string; + user_stake?: string; +} + +export interface ValidatorAveragePerformanceResult { + era_id: number; + public_key: string; + average_score: number; // between 0 and 100, treat it as percentage +} + +export interface AccountInfoResult { + account_hash: string; + url: string; + is_active: boolean; + deploy_hash: string; + verified_account_hashes: string[]; + info?: { + owner?: AccountInfoOwner; + nodes?: Array; + }; +} + +export interface AccountInfoOwner { + name?: string; + description?: string; + type?: Array; + email?: string; + identity?: { + ownership_disclosure_url?: string; + casper_association_kyc_url?: string; + casper_association_kyc_onchain?: string; + }; + resources?: { + code_of_conduct_url?: string; + terms_of_service_url?: string; + privacy_policy_url?: string; + other?: Array<{ + name?: string; + url?: string; + }>; + }; + affiliated_accounts?: Array; + website?: string; + branding?: { + logo?: { + svg?: string; + png_256?: string; + png_1024?: string; + }; + }; + location?: { + name?: string; + country?: string; + latitude?: number; + longitude?: number; + }; + social?: { + github?: string; + medium?: string; + reddit?: string; + wechat?: string; + keybase?: string; + twitter?: string; + youtube?: string; + facebook?: string; + telegram?: string; + }; +} + +export interface AccountInfoAffiliatedAccount { + public_key?: string; +} + +export interface AccountInfoNode { + public_key?: string; + description?: string; + functionality?: string[]; + location?: { + name?: string; + country?: string; + latitude?: number; + longitude?: number; + }; +} + +export interface DelegatorResult { + validator_public_key: string; + public_key: string; + stake: string; + bonding_purse: string; + account_info?: AccountInfoResult; + validator_account_info?: ValidatorAccountInfoResult; + validator: ValidatorResult; +} + +export interface ValidatorAccountInfoResult { + account_hash: string; + deploy_hash: string; + info: { + owner?: AccountInfoOwner; + nodes?: Array; + }; + is_active: boolean; + url: string; + verified_account_hashes: string[]; +} diff --git a/src/libs/services/validators-service/validators-service.ts b/src/libs/services/validators-service/validators-service.ts new file mode 100644 index 000000000..9ab6ee779 --- /dev/null +++ b/src/libs/services/validators-service/validators-service.ts @@ -0,0 +1,75 @@ +import { + getAuctionValidatorsUrl, + getValidatorsDetailsDataUrl +} from '@libs/services/validators-service/constants'; +import { handleError, toJson } from '@libs/services/utils'; +import { queryClient } from '@libs/services/query-client'; +import { dispatchToMainStore } from '@background/redux/utils'; +import { + ErrorResponse, + PaginatedResponse, + Payload +} from '@libs/services/types'; +import { + DelegatorResult, + ValidatorResult +} from '@libs/services/validators-service/types'; +import { serviceMessage } from '@background/service-message'; +import { VALIDATORS_REFRESH_RATE } from '@src/constants'; + +export const auctionValidatorsRequest = ( + casperApiUrl: string, + signal?: AbortSignal +) => + fetch(getAuctionValidatorsUrl(casperApiUrl), { signal }) + .then(toJson) + .catch(handleError); + +export const validatorsDetailsDataRequest = ( + casperApiUrl: string, + publicKey: string, + signal?: AbortSignal +) => + fetch(getValidatorsDetailsDataUrl(casperApiUrl, publicKey), { signal }) + .then(toJson) + .catch(handleError); + +export const fetchAuctionValidators = ({ + casperApiUrl +}: { + casperApiUrl: string; +}): Promise | ErrorResponse> => + queryClient.fetchQuery( + ['getAuctionValidators', casperApiUrl], + ({ signal }) => auctionValidatorsRequest(casperApiUrl, signal), + { + staleTime: VALIDATORS_REFRESH_RATE + } + ); + +export const fetchValidatorsDetailsData = ({ + casperApiUrl, + publicKey +}: { + casperApiUrl: string; + publicKey: string; +}): Promise | ErrorResponse> => + queryClient.fetchQuery( + ['getDelegations', casperApiUrl, publicKey], + ({ signal }) => + validatorsDetailsDataRequest(casperApiUrl, publicKey, signal), + { + staleTime: VALIDATORS_REFRESH_RATE + } + ); + +export const dispatchFetchAuctionValidatorsRequest = (): Promise< + Payload | ErrorResponse> +> => dispatchToMainStore(serviceMessage.fetchAuctionValidatorsRequest()); + +export const dispatchFetchValidatorsDetailsDataRequest = ( + publicKey: string +): Promise | ErrorResponse>> => + dispatchToMainStore( + serviceMessage.fetchValidatorsDetailsDataRequest({ publicKey }) + ); 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 41e667f36..a22f632e3 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 @@ -6,12 +6,12 @@ import styled from 'styled-components'; import { AccountActivityPlateContainer, ActivityPlateContentContainer, + ActivityPlateDivider, + ActivityPlateIconCircleContainer, AlignedFlexRow, AlignedSpaceBetweenFlexRow, - ActivityPlateIconCircleContainer, - ActivityPlateDivider, - SpacingSize, - RightAlignedCenteredFlexRow + RightAlignedCenteredFlexRow, + SpacingSize } from '@libs/layout'; import { ContentColor, @@ -36,11 +36,13 @@ import { } from '@libs/services/account-activity-service'; import { RouterPath, useTypedNavigate } from '@popup/router'; import { - ShortTypeName, - TransferType, - TypeColors, - TypeIcons, - TypeName + ActivityShortTypeName, + ActivityType, + ActivityTypeColors, + ActivityTypeIcons, + ActivityTypeName, + AuctionManagerEntryPoint, + TokenEntryPoint } from '@src/constants'; import { getAccountHashFromPublicKey } from '@libs/entities/Account'; import { getRecipientAddressFromTransaction } from '@libs/ui/utils/utils'; @@ -59,7 +61,11 @@ type Ref = HTMLDivElement; export const AccountActivityPlate = forwardRef( ({ transactionInfo, onClick, isDeploysList }, ref) => { - const [type, setType] = useState(null); + const [type, setType] = useState(null); + const [fromAccount, setFromAccount] = useState( + undefined + ); + const [toAccount, setToAccount] = useState(undefined); const navigate = useTypedNavigate(); const { t } = useTranslation(); @@ -119,21 +125,57 @@ export const AccountActivityPlate = forwardRef( : '-'; useEffect(() => { + if ('entryPoint' in transactionInfo) { + switch (transactionInfo.entryPoint?.name) { + case AuctionManagerEntryPoint.undelegate: { + setType(ActivityType.Undelegated); + setFromAccount(transactionInfo.args.validator?.parsed as string); + setToAccount(transactionInfo.args.delegator?.parsed as string); + return; + } + case AuctionManagerEntryPoint.delegate: { + setType(ActivityType.Delegated); + setFromAccount(transactionInfo.args.delegator?.parsed as string); + setToAccount(transactionInfo.args.validator?.parsed as string); + return; + } + case AuctionManagerEntryPoint.redelegate: { + setType(ActivityType.Redelegated); + setFromAccount(transactionInfo.args.validator?.parsed as string); + setToAccount(transactionInfo.args.new_validator?.parsed as string); + return; + } + case TokenEntryPoint.mint: { + setType(ActivityType.Mint); + setFromAccount(transactionInfo.callerPublicKey); + setToAccount(recipientAddress); + return; + } + case TokenEntryPoint.burn: { + setType(ActivityType.Burn); + setFromAccount(transactionInfo.callerPublicKey); + setToAccount(undefined); + return; + } + } + } + if (fromAccountPublicKey === activeAccount?.publicKey) { - setType(TransferType.Sent); + setType(ActivityType.Sent); } else if ( recipientAddress === activeAccount?.publicKey || recipientAddress === activeAccountHash ) { - setType(TransferType.Received); + setType(ActivityType.Received); } else { - setType(TransferType.Unknown); + setType(ActivityType.Unknown); } }, [ fromAccountPublicKey, activeAccount?.publicKey, recipientAddress, - activeAccountHash + activeAccountHash, + transactionInfo ]); return ( @@ -144,8 +186,8 @@ export const AccountActivityPlate = forwardRef( navigate(RouterPath.ActivityDetails, { state: { activityDetailsData: { - fromAccount: fromAccountPublicKey, - toAccount: recipientAddress, + fromAccount: fromAccount || fromAccountPublicKey, + toAccount: toAccount || recipientAddress, deployHash, type, amount: formattedAmount, @@ -162,9 +204,9 @@ export const AccountActivityPlate = forwardRef( {type != null && ( )} @@ -175,8 +217,8 @@ export const AccountActivityPlate = forwardRef( {type != null && (formattedAmount.length >= 13 - ? ShortTypeName[type] - : TypeName[type])} + ? ActivityShortTypeName[type] + : ActivityTypeName[type])} @@ -186,7 +228,9 @@ export const AccountActivityPlate = forwardRef( formattedAmount ) : ( <> - {type === TransferType.Sent ? '-' : ''} + {type === ActivityType.Sent || type === ActivityType.Delegated + ? '-' + : ''} {formattedAmount} )} diff --git a/src/libs/ui/components/account-casper-activity-plate/account-casper-activity-plate.tsx b/src/libs/ui/components/account-casper-activity-plate/account-casper-activity-plate.tsx index f5b75b2d1..eccb9a2a6 100644 --- a/src/libs/ui/components/account-casper-activity-plate/account-casper-activity-plate.tsx +++ b/src/libs/ui/components/account-casper-activity-plate/account-casper-activity-plate.tsx @@ -28,11 +28,11 @@ import { SpacingSize } from '@libs/layout'; import { - ShortTypeName, - TransferType, - TypeColors, - TypeIcons, - TypeName + ActivityShortTypeName, + ActivityType, + ActivityTypeColors, + ActivityTypeIcons, + ActivityTypeName } from '@src/constants'; import { TransferResultWithId } from '@libs/services/account-activity-service'; import { getAccountHashFromPublicKey } from '@libs/entities/Account'; @@ -47,7 +47,7 @@ export const AccountCasperActivityPlate = forwardRef< Ref, AccountCasperActivityPlateProps >(({ transactionInfo, onClick }, ref) => { - const [type, setType] = useState(null); + const [type, setType] = useState(null); const navigate = useTypedNavigate(); const { t } = useTranslation(); @@ -77,14 +77,14 @@ export const AccountCasperActivityPlate = forwardRef< fromAccountPublicKey === activeAccount?.publicKey || fromAccount === activeAccountHash ) { - setType(TransferType.Sent); + setType(ActivityType.Sent); } else if ( toAccountPublicKey === activeAccount?.publicKey || toAccount === activeAccountHash ) { - setType(TransferType.Received); + setType(ActivityType.Received); } else { - setType(TransferType.Unknown); + setType(ActivityType.Unknown); } }, [ fromAccountPublicKey, @@ -119,9 +119,9 @@ export const AccountCasperActivityPlate = forwardRef< {type != null && ( )} @@ -132,13 +132,13 @@ export const AccountCasperActivityPlate = forwardRef< {type != null && (formattedAmount.length >= 13 - ? ShortTypeName[type] - : TypeName[type])} + ? ActivityShortTypeName[type] + : ActivityTypeName[type])} - {type === TransferType.Sent ? '-' : ''} + {type === ActivityType.Sent ? '-' : ''} {formattedAmount} diff --git a/src/libs/ui/components/account-list/account-list.tsx b/src/libs/ui/components/account-list/account-list.tsx index 44db661d3..dd1f85a64 100644 --- a/src/libs/ui/components/account-list/account-list.tsx +++ b/src/libs/ui/components/account-list/account-list.tsx @@ -61,7 +61,7 @@ const ButtonContainer = styled(CenteredFlexRow)` `; interface AccountListProps { - closeModal: (e: React.MouseEvent) => void; + closeModal: (e: React.MouseEvent) => void; } export const AccountList = ({ closeModal }: AccountListProps) => { @@ -143,7 +143,10 @@ export const AccountList = ({ closeModal }: AccountListProps) => { /> - + {isConnected && ( diff --git a/src/libs/ui/components/account-popover/account-popover.tsx b/src/libs/ui/components/account-popover/account-popover.tsx index 960f83d2f..d8cc3182e 100644 --- a/src/libs/ui/components/account-popover/account-popover.tsx +++ b/src/libs/ui/components/account-popover/account-popover.tsx @@ -17,9 +17,11 @@ import { Account } from '@background/redux/vault/types'; interface AccountActionsMenuPopoverProps { account: Account; + onClick?: (e: React.MouseEvent) => void; } export const AccountActionsMenuPopover = ({ - account + account, + onClick }: AccountActionsMenuPopoverProps) => { const navigate = useTypedNavigate(); const { t } = useTranslation(); @@ -40,9 +42,13 @@ export const AccountActionsMenuPopover = ({ {connectedAccountNames.includes(account.name) ? ( { - closePopover(e); + onClick={event => { + closePopover(event); activeOrigin && disconnectAccount(account.name, activeOrigin); + + if (onClick) { + onClick(event); + } }} > + onClick={event => { navigate( isAnyAccountConnected ? `${RouterPath.ConnectAnotherAccount}/${account.name}` : RouterPath.NoConnectedAccount - ) - } + ); + + if (onClick) { + onClick(event); + } + }} > + onClick={event => { navigate( RouterPath.RenameAccount.replace(':accountName', account.name) - ) - } + ); + + if (onClick) { + onClick(event); + } + }} > + onClick={event => { navigate( RouterPath.AccountSettings.replace(':accountName', account.name) - ) - } + ); + + if (onClick) { + onClick(event); + } + }} > ({ const ConnectionStatusBadgeContainer = styled(AlignedFlexRow)` position: relative; + z-index: 1; `; export const BackgroundWrapper = styled.div( diff --git a/src/libs/ui/components/button/button.tsx b/src/libs/ui/components/button/button.tsx index 798eae925..cec9df596 100644 --- a/src/libs/ui/components/button/button.tsx +++ b/src/libs/ui/components/button/button.tsx @@ -42,7 +42,6 @@ const BaseButton = styled.button( ...(circle && { borderRadius: '24px', - margin: '0 16px', padding: '12px' }), diff --git a/src/libs/ui/components/error/error.tsx b/src/libs/ui/components/error/error.tsx new file mode 100644 index 000000000..319c0b48e --- /dev/null +++ b/src/libs/ui/components/error/error.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import styled from 'styled-components'; +import { Trans, useTranslation } from 'react-i18next'; + +import { AlignedFlexRow, FlexColumn, SpacingSize } from '@libs/layout'; +import { SvgIcon, Typography } from '@libs/ui'; + +const ErrorContainer = styled(FlexColumn)` + padding: 12px 16px; + + background: ${({ theme }) => theme.color.backgroundPrimary}; +`; + +interface ErrorProps { + header: string; + description: string; +} + +export const Error = ({ header, description }: ErrorProps) => { + const { t } = useTranslation(); + + return ( + + + + + {header} + + + + {description} + + + ); +}; diff --git a/src/libs/ui/components/hash/hash.tsx b/src/libs/ui/components/hash/hash.tsx index 09c672b2f..8e16b5011 100644 --- a/src/libs/ui/components/hash/hash.tsx +++ b/src/libs/ui/components/hash/hash.tsx @@ -16,7 +16,8 @@ import { truncateKey, TruncateKeySize } from './utils'; export enum HashVariant { CaptionHash = 'captionHash', - BodyHash = 'bodyHash' + BodyHash = 'bodyHash', + ListSubtextHash = 'listSubtextHash' } interface HashContainerProps { diff --git a/src/libs/ui/components/hash/utils.ts b/src/libs/ui/components/hash/utils.ts index 6291b1acc..581626a37 100644 --- a/src/libs/ui/components/hash/utils.ts +++ b/src/libs/ui/components/hash/utils.ts @@ -12,8 +12,8 @@ export function truncateKey( break; case 'small': default: - beginOfKey = key.slice(0, 5); - endOfKey = key.slice(key.length - 5); + beginOfKey = key.slice(0, 4); + endOfKey = key.slice(key.length - 4); break; case 'medium': diff --git a/src/libs/ui/components/input/input.tsx b/src/libs/ui/components/input/input.tsx index 95ee67572..cc0249278 100644 --- a/src/libs/ui/components/input/input.tsx +++ b/src/libs/ui/components/input/input.tsx @@ -170,7 +170,7 @@ export const Input = React.forwardRef(function Input( : { [InputValidationType.Password]: { type: 'password', - min: '12', + min: '16', max: '0', step: '0' } @@ -231,5 +231,3 @@ export const Input = React.forwardRef(function Input( ); }); - -export default Input; diff --git a/src/libs/ui/components/list/list.tsx b/src/libs/ui/components/list/list.tsx index b5dffae12..5cf3e462c 100644 --- a/src/libs/ui/components/list/list.tsx +++ b/src/libs/ui/components/list/list.tsx @@ -44,7 +44,7 @@ const RowContainer = styled(FlexColumn)``; const ListHeaderContainer = styled(FlexColumn)` ${({ stickyHeader, theme }) => stickyHeader - ? `position: sticky; top: 72px; z-index: 1; background: ${theme.color.backgroundSecondary}};` + ? `position: sticky; top: 72px; z-index: 2; background: ${theme.color.backgroundSecondary}};` : ''}; &::after { @@ -79,6 +79,7 @@ interface ListProps { marginLeftForItemSeparatorLine: number; stickyHeader?: boolean; maxHeight?: number; + borderRadius?: 'base'; } export function List({ @@ -93,8 +94,14 @@ export function List({ headerLabelTop = SpacingSize.XL, contentTop = SpacingSize.XL, stickyHeader, - maxHeight + maxHeight, + borderRadius }: ListProps) { + const separatorLine = + marginLeftForHeaderSeparatorLine || marginLeftForHeaderSeparatorLine === 0 + ? marginLeftForHeaderSeparatorLine + : marginLeftForItemSeparatorLine; + return ( <> {headerLabel && ( @@ -119,13 +126,10 @@ export function List({ )} - + {renderHeader && ( {renderHeader()} diff --git a/src/libs/ui/components/modal/modal.tsx b/src/libs/ui/components/modal/modal.tsx index 439e8ec5b..46fdc775e 100644 --- a/src/libs/ui/components/modal/modal.tsx +++ b/src/libs/ui/components/modal/modal.tsx @@ -9,34 +9,39 @@ const ChildrenContainer = styled(AlignedFlexRow)` cursor: pointer; `; -const ModalContainer = styled.div` - position: fixed; - top: 88px; - left: 0; - right: 0; +const ModalContainer = styled.div<{ placement: 'top' | 'bottom' }>( + ({ theme, placement }) => ({ + position: 'fixed', + top: placement === 'top' ? '88px' : undefined, + bottom: placement === 'bottom' ? '16px' : undefined, + left: 0, + right: 0, - margin: 0 16px; + margin: '0 16px', - max-width: 328px; + maxWidth: '328px', + + backgroundColor: theme.color.backgroundPrimary, + boxShadow: theme.shadow.contextMenu, + borderRadius: `${theme.borderRadius.twelve}px` + }) +); - background-color: ${({ theme }) => theme.color.backgroundPrimary}; - box-shadow: ${({ theme }) => theme.shadow.contextMenu}; - border-radius: ${({ theme }) => theme.borderRadius.twelve}px; -`; interface RenderChildrenProps { isOpen: boolean; } interface RenderContentProps { - closeModal: (e: MouseEvent) => void; + closeModal: (e: MouseEvent) => void; } export interface ModalProps extends BaseProps { children: (renderProps: RenderChildrenProps) => React.ReactNode | string; renderContent: (renderProps: RenderContentProps) => React.ReactNode | string; + placement: 'top' | 'bottom'; } -export const Modal = ({ children, renderContent }: ModalProps) => { +export const Modal = ({ children, renderContent, placement }: ModalProps) => { const [isOpen, setIsOpen] = useState(false); const childrenContainerRef = useRef(null); @@ -47,7 +52,7 @@ export const Modal = ({ children, renderContent }: ModalProps) => { } }); - const closeModal = (e: MouseEvent) => { + const closeModal = (e: MouseEvent) => { e.stopPropagation(); setIsOpen(false); }; @@ -66,7 +71,7 @@ export const Modal = ({ children, renderContent }: ModalProps) => { {isOpen && ( - + {renderContent({ closeModal })} diff --git a/src/libs/ui/components/recipient-plate/recipient-plate.tsx b/src/libs/ui/components/recipient-plate/recipient-plate.tsx index dd6bbde9b..dabefbc4d 100644 --- a/src/libs/ui/components/recipient-plate/recipient-plate.tsx +++ b/src/libs/ui/components/recipient-plate/recipient-plate.tsx @@ -17,7 +17,7 @@ const PublicKeyOptionContainer = styled(FlexRow)<{ onClick?: () => void }>` padding: 12px 16px; background-color: ${({ theme }) => theme.color.backgroundPrimary}; - border-radius: ${({ theme }) => theme.borderRadius.eight}px; + border-radius: ${({ theme }) => theme.borderRadius.base}px; `; export const RecipientPlate = ({ diff --git a/src/libs/ui/components/tabs/tabs.tsx b/src/libs/ui/components/tabs/tabs.tsx index ad239b6e8..abb601ca4 100644 --- a/src/libs/ui/components/tabs/tabs.tsx +++ b/src/libs/ui/components/tabs/tabs.tsx @@ -14,7 +14,7 @@ const TabsContainer = styled(AlignedSpaceBetweenFlexRow)` const StickyTabsContainer = styled.div` position: sticky; - top: -2px; + top: 0; z-index: 5; padding: 16px 0; diff --git a/src/libs/ui/components/tile/tile.tsx b/src/libs/ui/components/tile/tile.tsx index a00c2c64d..47a21f333 100644 --- a/src/libs/ui/components/tile/tile.tsx +++ b/src/libs/ui/components/tile/tile.tsx @@ -1,8 +1,9 @@ import styled from 'styled-components'; -export const Tile = styled.div` +export const Tile = styled.div<{ borderRadius?: 'base' }>` width: 100%; background-color: ${({ theme }) => theme.color.backgroundPrimary}; - border-radius: ${({ theme }) => theme.borderRadius.twelve}px; + border-radius: ${({ theme, borderRadius }) => + borderRadius ? theme.borderRadius.base : theme.borderRadius.twelve}px; `; diff --git a/src/libs/ui/components/transfer-success-screen/transfer-succeess-screen.tsx b/src/libs/ui/components/transfer-success-screen/transfer-succeess-screen.tsx index c5f01e586..d78e94930 100644 --- a/src/libs/ui/components/transfer-success-screen/transfer-succeess-screen.tsx +++ b/src/libs/ui/components/transfer-success-screen/transfer-succeess-screen.tsx @@ -10,11 +10,11 @@ import { import { SvgIcon, Typography } from '@libs/ui'; interface TransferSuccessScreenProps { - isNftTransfer?: boolean; + headerText: string; } export const TransferSuccessScreen = ({ - isNftTransfer = false + headerText }: TransferSuccessScreenProps) => { const { t } = useTranslation(); @@ -28,11 +28,7 @@ export const TransferSuccessScreen = ({ /> - - {isNftTransfer - ? 'You’ve sent the NFT' - : 'You submitted a transaction'} - + {headerText} diff --git a/src/libs/ui/components/typography/typography.tsx b/src/libs/ui/components/typography/typography.tsx index d287c8439..c47cdf16a 100644 --- a/src/libs/ui/components/typography/typography.tsx +++ b/src/libs/ui/components/typography/typography.tsx @@ -23,7 +23,8 @@ export type TypographyType = | 'CSPRBold' | 'listSubtext' | 'formFieldStatus' // TODO: Temporary name. Make a better name - | 'subtitle'; + | 'subtitle' + | 'listSubtextHash'; export type CSPRSize = '2.8rem' | '2.4rem' | '2rem' | '1.8rem'; @@ -200,6 +201,14 @@ const StyledTypography = styled('span').withConfig({ fontWeight: theme.typography.fontWeight.bold }; + case 'listSubtextHash': + return { + ...CSPRBase, + fontSize: '1.2rem', + lineHeight: '1.6rem', + fontWeight: theme.typography.fontWeight.medium + }; + default: throw new Error('Unknown type of Typography'); } diff --git a/src/libs/ui/components/validator-dropdown-input/validator-dropdown-input.tsx b/src/libs/ui/components/validator-dropdown-input/validator-dropdown-input.tsx new file mode 100644 index 000000000..c51b265ba --- /dev/null +++ b/src/libs/ui/components/validator-dropdown-input/validator-dropdown-input.tsx @@ -0,0 +1,231 @@ +import React, { useCallback, useEffect, useState } from 'react'; +import { UseFormReturn, useWatch } from 'react-hook-form'; +import { Trans, useTranslation } from 'react-i18next'; +import styled from 'styled-components'; + +import { + AlignedSpaceBetweenFlexRow, + SpacingSize, + VerticalSpaceContainer +} from '@src/libs/layout'; +import { SvgIcon, Input, List, ValidatorPlate, Typography } from '@libs/ui'; +import { StakeValidatorFormValues } from '@libs/ui/forms/stakes-form'; +import { useClickAway } from '@libs/ui/hooks/use-click-away'; +import { ValidatorResultWithId } from '@libs/services/validators-service/types'; +import { AuctionManagerEntryPoint } from '@src/constants'; + +const DropDownHeader = styled(AlignedSpaceBetweenFlexRow)` + padding: 8px 16px; + + border-top-left-radius: ${({ theme }) => theme.borderRadius.base}px; + border-top-right-radius: ${({ theme }) => theme.borderRadius.base}px; + + background-color: ${({ theme }) => theme.color.backgroundPrimary}; +`; + +interface ValidatorDropdownInputProps { + validatorForm: UseFormReturn; + validatorList: ValidatorResultWithId[] | null; + validator: ValidatorResultWithId | null; + setValidator: React.Dispatch< + React.SetStateAction + >; + setStakeAmount: React.Dispatch>; + stakesType: AuctionManagerEntryPoint; +} + +export const ValidatorDropdownInput = ({ + validatorForm, + validatorList, + validator, + setValidator, + setStakeAmount, + stakesType +}: ValidatorDropdownInputProps) => { + const [isOpenValidatorPublicKeysList, setIsOpenValidatorPublicKeysList] = + useState(true); + const [showValidatorPlate, setShowValidatorPlate] = useState(false); + const [label, setLabel] = useState(''); + + const { t } = useTranslation(); + + const { register, formState, setValue, control, trigger } = validatorForm; + const { errors } = formState; + + const inputValue = useWatch({ + control: control, + name: 'validatorPublicKey' + }); + + const { ref: clickAwayRef } = useClickAway({ + callback: async () => { + setIsOpenValidatorPublicKeysList(false); + + if (validator && inputValue !== '') { + setShowValidatorPlate(true); + setValue('validatorPublicKey', validator.public_key); + setStakeAmount(validator.user_stake!); + await trigger('validatorPublicKey'); + return; + } else if (validator && inputValue === '') { + setShowValidatorPlate(false); + setValue('validatorPublicKey', ''); + setValidator(null); + await trigger('validatorPublicKey'); + return; + } + + setValue('validatorPublicKey', ''); + await trigger('validatorPublicKey'); + } + }); + + useEffect(() => { + trigger('validatorPublicKey'); + }, [trigger, validator]); + + useEffect(() => { + if (formState.isValid) { + setShowValidatorPlate(true); + } + // This should trigger only once + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const useFilteredValidators = ( + inputValue: string, + validatorList: ValidatorResultWithId[] | null + ) => { + const filterValidators = useCallback( + ( + inputValue: string, + validatorList: ValidatorResultWithId[] | null + ): ValidatorResultWithId[] | [] => { + if (!validatorList) return []; + if (!inputValue) return validatorList; + + return validatorList.filter(validator => { + const { public_key } = validator; + if (validator?.account_info?.info?.owner?.name) { + const { name } = validator.account_info.info.owner; + + return ( + name?.toLowerCase().includes(inputValue?.toLowerCase()) || + public_key?.toLowerCase().includes(inputValue?.toLowerCase()) + ); + } + + return public_key?.toLowerCase().includes(inputValue?.toLowerCase()); + }); + }, + [] + ); + + return filterValidators(inputValue, validatorList); + }; + + const filteredValidatorsList = useFilteredValidators( + inputValue, + validatorList + ); + + useEffect(() => { + switch (stakesType) { + case AuctionManagerEntryPoint.delegate: { + setLabel('To validator'); + break; + } + case AuctionManagerEntryPoint.undelegate: { + setLabel('From validator'); + break; + } + + default: + throw Error('fetch validator: unknown stakes type'); + } + }, [stakesType]); + + return showValidatorPlate && validator ? ( + + { + setShowValidatorPlate(false); + setIsOpenValidatorPublicKeysList(true); + }} + /> + + ) : ( + { + setIsOpenValidatorPublicKeysList(true); + }} + > + {/*TODO: create Select component and rewrite this*/} + } + suffixIcon={ + + } + placeholder={t('Validator public address')} + {...register('validatorPublicKey')} + autoComplete="off" + /> + {isOpenValidatorPublicKeysList && ( + ( + + + Validator + + + Total stake, fee, delegators + + + )} + renderRow={validator => ( + { + setValue('validatorPublicKey', validator.public_key); + setStakeAmount(validator.user_stake!); + + setValidator(validator); + + setIsOpenValidatorPublicKeysList(false); + setShowValidatorPlate(true); + }} + /> + )} + marginLeftForItemSeparatorLine={56} + marginLeftForHeaderSeparatorLine={0} + /> + )} + + ); +}; diff --git a/src/libs/ui/components/validator-plate/validator-plate.tsx b/src/libs/ui/components/validator-plate/validator-plate.tsx new file mode 100644 index 000000000..db62bb484 --- /dev/null +++ b/src/libs/ui/components/validator-plate/validator-plate.tsx @@ -0,0 +1,188 @@ +import React, { useEffect, useState } from 'react'; +import styled from 'styled-components'; +import { FieldError } from 'react-hook-form'; + +import { + AlignedFlexRow, + AlignedSpaceBetweenFlexRow, + FlexColumn, + FlexRow, + RightAlignedFlexColumn, + SpacingSize, + VerticalSpaceContainer +} from '@libs/layout'; +import { + Avatar, + Error, + FormField, + Hash, + HashVariant, + Typography +} from '@libs/ui'; +import { formatNumber, motesToCSPR } from '@libs/ui/utils/formatters'; +import { getImageProxyUrl } from '@src/utils'; + +const ValidatorPlateContainer = styled(AlignedSpaceBetweenFlexRow)<{ + onClick?: () => void; + withBackground?: boolean; +}>` + cursor: ${({ onClick }) => (onClick ? 'pointer' : 'initial')}; + background: ${({ withBackground, theme }) => + withBackground ? theme.color.backgroundPrimary : 'transparent'}; + border-radius: ${({ theme }) => theme.borderRadius.base}px; + + padding: 8px 16px; +`; + +const NameContainer = styled(FlexColumn)` + max-width: 93px; +`; + +const IconContainer = styled.div` + padding-top: 8px; +`; + +const Image = styled.img` + height: 24px; + width: 24px; +`; + +interface ValidatorPlateProps { + handleClick?: () => void; + publicKey: string; + name?: string; + logo?: string; + showFullPublicKey?: boolean; + fee: number; + delegatorsNumber?: number; + validatorLabel?: string; + error?: FieldError; + totalStake?: string; +} + +export const ValidatorPlate = ({ + publicKey, + name, + showFullPublicKey, + fee, + handleClick, + logo, + delegatorsNumber, + validatorLabel, + error, + totalStake +}: ValidatorPlateProps) => { + const [formattedTotalStake, setFormattedTotalStake] = useState(''); + + useEffect(() => { + if (totalStake) { + setFormattedTotalStake(formatNumber(motesToCSPR(totalStake))); + } + }, [totalStake]); + + const logoUrl = getImageProxyUrl(logo); + const formattedFee = formatNumber(fee, { + precision: { min: 2 } + }); + const getFormattedDelegatorsNumber = () => { + if (delegatorsNumber && delegatorsNumber >= 1000) { + return ( + formatNumber(delegatorsNumber / 1000, { + precision: { max: 2 } + }) + 'k' + ); + } + + return delegatorsNumber; + }; + + const plateWithFullPublicKey = ( + + + + {logoUrl ? ( + {name} + ) : ( + + )} + + + + + {name} + + + + + ); + + const plate = (withBackground?: boolean) => ( + + + {logoUrl ? ( + {name} + ) : ( + + )} + + + + {name} + + + + + + {`${formattedTotalStake} CSPR`} + + + + {`${formattedFee}% fee`} + + + {getFormattedDelegatorsNumber()} delegators + + + + + ); + + if (validatorLabel) { + return ( + <> + + {showFullPublicKey ? plateWithFullPublicKey : plate(true)} + + {error && error.message && ( + + + + )} + + ); + } + + return showFullPublicKey ? plateWithFullPublicKey : plate(); +}; diff --git a/src/libs/ui/forms/form-validation-rules.ts b/src/libs/ui/forms/form-validation-rules.ts index e5ef55850..a8cfda497 100644 --- a/src/libs/ui/forms/form-validation-rules.ts +++ b/src/libs/ui/forms/form-validation-rules.ts @@ -8,9 +8,13 @@ import { dispatchToMainStore } from '@src/background/redux/utils'; import { loginRetryCountIncremented } from '@src/background/redux/login-retry-count/actions'; import { selectLoginRetryCount } from '@background/redux/login-retry-count/selectors'; import { + STAKE_COST_MOTES, + DELEGATION_MIN_AMOUNT_MOTES, LOGIN_RETRY_ATTEMPTS_LIMIT, + MAX_DELEGATORS, + TRANSFER_COST_MOTES, TRANSFER_MIN_AMOUNT_MOTES, - TRANSFER_COST_MOTES + AuctionManagerEntryPoint } from '@src/constants'; import { isValidPublicKey, isValidU64 } from '@src/utils'; import { CSPRtoMotes, motesToCSPR } from '@libs/ui/utils/formatters'; @@ -128,7 +132,7 @@ export const useRecipientPublicKeyRule = () => { }); }; -export const useCsprAmountRule = (amountMotes: string | null) => { +export const useCSPRTransferAmountRule = (amountMotes: string | null) => { const { t } = useTranslation(); const maxAmountMotes: string = @@ -269,3 +273,117 @@ export const usePaymentAmountRule = (csprBalance: string | null) => { ) }); }; + +export const useCSPRStakeAmountRule = ( + amountMotes: string | null, + mode: AuctionManagerEntryPoint, + stakeAmountMotes: string +) => { + const { t } = useTranslation(); + + const getStakeMinAmountMotes = () => { + switch (mode) { + case AuctionManagerEntryPoint.delegate: { + return DELEGATION_MIN_AMOUNT_MOTES; + } + case AuctionManagerEntryPoint.undelegate: { + return '0'; + } + + default: { + return DELEGATION_MIN_AMOUNT_MOTES; + } + } + }; + + const maxAmountMotes: string = + amountMotes == null + ? '0' + : Big(amountMotes).sub(STAKE_COST_MOTES).toString(); + + return Yup.string() + .required({ + header: t('Amount is required'), + description: t('You need to enter an amount to stake') + }) + .test({ + name: 'validU64', + test: csprAmountInputValue => { + if (csprAmountInputValue) { + return isValidU64(csprAmountInputValue); + } + + return false; + }, + message: { + header: t(`Amount is invalid`), + description: t(`You need to enter a valid amount`) + } + }) + .test({ + name: 'amountBelowMinTransfer', + test: csprAmountInputValue => { + if (csprAmountInputValue) { + return Big(CSPRtoMotes(csprAmountInputValue)).gte( + getStakeMinAmountMotes() + ); + } + + return false; + }, + message: { + header: t('You can’t delegate this amount'), + description: t( + `The minimum required delegation amount is ${motesToCSPR( + getStakeMinAmountMotes() + )} CSPR.` + ) + } + }) + .test({ + name: 'amountAboveBalance', + test: csprAmountInputValue => { + if (csprAmountInputValue) { + if (mode === AuctionManagerEntryPoint.undelegate) { + return Big(CSPRtoMotes(csprAmountInputValue)).lte( + Big(stakeAmountMotes).sub(getStakeMinAmountMotes()).toString() + ); + } + return Big(CSPRtoMotes(csprAmountInputValue)).lte(maxAmountMotes); + } + + return false; + }, + message: + mode === AuctionManagerEntryPoint.undelegate + ? { + header: t('You can’t undelegate this amount'), + description: t('Amount must be less than staked CSPR.') + } + : { + header: t('Your account balance is not high enough'), + description: t( + 'Your account balance is not high enough. Enter a smaller amount.' + ) + } + }); +}; + +export const useValidatorPublicKeyRule = (delegatorsNumber?: number) => { + const { t } = useTranslation(); + + return Yup.string() + .required(t('Recipient is required')) + .test({ + name: 'validatorPublicKey', + test: value => (value ? isValidPublicKey(value) : false), + message: t('Recipient should be a valid public key') + }) + .test({ + name: 'maxDelegators', + test: () => !(delegatorsNumber && delegatorsNumber >= MAX_DELEGATORS), + 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 new file mode 100644 index 000000000..5a96a6ab7 --- /dev/null +++ b/src/libs/ui/forms/stakes-form.ts @@ -0,0 +1,50 @@ +import * as Yup from 'yup'; +import { useForm } from 'react-hook-form'; +import { UseFormProps } from 'react-hook-form/dist/types/form'; +import { yupResolver } from '@hookform/resolvers/yup/dist/yup'; + +import { + useCSPRStakeAmountRule, + useValidatorPublicKeyRule +} from '@libs/ui/forms/form-validation-rules'; +import { AuctionManagerEntryPoint } from '@src/constants'; + +export type StakeValidatorFormValues = { + validatorPublicKey: string; +}; + +export type StakeAmountFormValues = { + amount: string; +}; + +export const useStakesForm = ( + amountMotes: string | null, + stakesType: AuctionManagerEntryPoint, + stakeAmountMotes: string, + delegatorsNumber?: number +) => { + const validatorFormSchema = Yup.object().shape({ + validatorPublicKey: useValidatorPublicKeyRule(delegatorsNumber) + }); + + const validatorFormOptions: UseFormProps = { + reValidateMode: 'onChange', + mode: 'onChange', + resolver: yupResolver(validatorFormSchema) + }; + + const amountFormSchema = Yup.object().shape({ + amount: useCSPRStakeAmountRule(amountMotes, stakesType, stakeAmountMotes) + }); + + const amountFormOptions: UseFormProps = { + reValidateMode: 'onChange', + mode: 'onChange', + resolver: yupResolver(amountFormSchema) + }; + + return { + validatorForm: useForm(validatorFormOptions), + amountForm: useForm(amountFormOptions) + }; +}; diff --git a/src/libs/ui/forms/transfer.ts b/src/libs/ui/forms/transfer.ts index 4adaf0d6d..9580ba518 100644 --- a/src/libs/ui/forms/transfer.ts +++ b/src/libs/ui/forms/transfer.ts @@ -3,7 +3,7 @@ import { useForm } from 'react-hook-form'; import { UseFormProps } from 'react-hook-form/dist/types/form'; import { - useCsprAmountRule, + useCSPRTransferAmountRule, useErc20AmountRule, usePaymentAmountRule, useRecipientPublicKeyRule, @@ -46,7 +46,7 @@ export function useTransferForm( }); const csprAmountFormSchema = Yup.object().shape({ - amount: useCsprAmountRule(amountMotes), + amount: useCSPRTransferAmountRule(amountMotes), transferIdMemo: useTransferIdMemoRule() }); diff --git a/src/libs/ui/index.ts b/src/libs/ui/index.ts index 83c5e446f..bf044dc0a 100644 --- a/src/libs/ui/index.ts +++ b/src/libs/ui/index.ts @@ -45,6 +45,9 @@ export * from './components/contract-icon/contract-icon'; export * from './components/password-inputs/password-inputs'; export * from './components/toggle/toggle'; export * from './components/skeleton/skeleton'; +export * from './components/validator-dropdown-input/validator-dropdown-input'; +export * from './components/validator-plate/validator-plate'; +export * from './components/error/error'; export * from './utils/match-media'; export * from './utils/match-size'; diff --git a/xcode-project/Casper Wallet/Casper Wallet.xcodeproj/project.pbxproj b/xcode-project/Casper Wallet/Casper Wallet.xcodeproj/project.pbxproj index ba296e19e..77f8baf9d 100644 --- a/xcode-project/Casper Wallet/Casper Wallet.xcodeproj/project.pbxproj +++ b/xcode-project/Casper Wallet/Casper Wallet.xcodeproj/project.pbxproj @@ -564,7 +564,7 @@ CODE_SIGN_ENTITLEMENTS = "Casper Wallet/Casper Wallet.entitlements"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 35; + CURRENT_PROJECT_VERSION = 37; ENABLE_HARDENED_RUNTIME = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "Casper Wallet/Info.plist"; @@ -578,7 +578,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.14; - MARKETING_VERSION = 1.6.3; + MARKETING_VERSION = 1.7.0; OTHER_LDFLAGS = ( "-framework", SafariServices, @@ -601,7 +601,7 @@ CODE_SIGN_ENTITLEMENTS = "Casper Wallet/Casper Wallet.entitlements"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 35; + CURRENT_PROJECT_VERSION = 37; ENABLE_HARDENED_RUNTIME = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "Casper Wallet/Info.plist"; @@ -615,7 +615,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.14; - MARKETING_VERSION = 1.6.3; + MARKETING_VERSION = 1.7.0; OTHER_LDFLAGS = ( "-framework", SafariServices,