From 01d47ce69c4a1d621c77998b45e91a2c5c095f0f Mon Sep 17 00:00:00 2001 From: BoThe1K Date: Wed, 11 Oct 2023 16:36:56 +0200 Subject: [PATCH 1/2] feat/qb-2037: Add new chart with updated metrics --- .pnp.cjs | 555 ++++++++++++++++++ .../src/graphql/useApollo/index.ts | 2 +- apps/web-stratos/package.json | 3 + apps/web-stratos/src/chain.json | 8 +- .../nav/components/title_bar/index.tsx | 40 ++ .../nav/components/title_bar/utils.tsx | 59 ++ .../src/pages/withGetServerSideProps.ts | 18 + .../src/pages/withGetStaticProps.ts | 27 + .../app/__snapshots__/index.test.tsx.snap | 235 ++++++++ .../src/screens/app/components/index.ts | 4 + .../__snapshots__/index.test.tsx.snap | 7 + .../app/components/inner_app/hooks.test.tsx | 21 + .../screens/app/components/inner_app/hooks.ts | 37 ++ .../app/components/inner_app/index.test.tsx | 42 ++ .../app/components/inner_app/index.tsx | 14 + .../src/screens/app/components/main/hooks.ts | 30 + .../src/screens/app/components/main/index.tsx | 105 ++++ .../app/components/recoil_debugger/index.tsx | 18 + apps/web-stratos/src/screens/app/hooks.ts | 43 ++ .../src/screens/app/index.test.tsx | 30 + apps/web-stratos/src/screens/app/index.tsx | 49 ++ apps/web-stratos/src/screens/app/utils.tsx | 64 ++ .../blocks/__snapshots__/index.test.tsx.snap | 164 ++++++ .../desktop/__snapshots__/index.test.tsx.snap | 344 +++++++++++ .../blocks/components/desktop/index.test.tsx | 38 ++ .../blocks/components/desktop/index.tsx | 124 ++++ .../blocks/components/desktop/styles.ts | 20 + .../blocks/components/desktop/utils.tsx | 22 + .../components/blocks/components/index.ts | 4 + .../mobile/__snapshots__/index.test.tsx.snap | 33 ++ .../blocks/components/mobile/index.test.tsx | 42 ++ .../blocks/components/mobile/index.tsx | 55 ++ .../screens/home/components/blocks/hooks.ts | 50 ++ .../home/components/blocks/index.test.tsx | 81 +++ .../screens/home/components/blocks/index.tsx | 54 ++ .../screens/home/components/blocks/styles.ts | 28 + .../screens/home/components/blocks/types.ts | 14 + .../home/components/charts/TokenomicChart.tsx | 23 + .../home/components/consensus/hooks.ts | 219 +++++++ .../home/components/consensus/index.tsx | 107 ++++ .../home/components/consensus/styles.ts | 60 ++ .../__snapshots__/index.test.tsx.snap | 403 +++++++++++++ .../data_blocks/components/index.ts | 3 + .../__snapshots__/index.test.tsx.snap | 105 ++++ .../components/single_block/index.test.tsx | 22 + .../components/single_block/index.tsx | 36 ++ .../components/single_block/styles.ts | 37 ++ .../home/components/data_blocks/hooks.ts | 107 ++++ .../components/data_blocks/index.test.tsx | 110 ++++ .../home/components/data_blocks/index.tsx | 57 ++ .../home/components/data_blocks/styles.ts | 30 + .../home/components/hero/components/index.ts | 4 + .../__snapshots__/index.test.tsx.snap | 487 +++++++++++++++ .../components/online_voting_power/hooks.ts | 64 ++ .../online_voting_power/index.test.tsx | 84 +++ .../components/online_voting_power/index.tsx | 82 +++ .../components/online_voting_power/styles.ts | 71 +++ .../hero/components/token_price/hooks.ts | 191 ++++++ .../hero/components/token_price/index.tsx | 21 + .../hero/components/token_price/styles.ts | 10 + .../src/screens/home/components/hero/hooks.ts | 52 ++ .../screens/home/components/hero/index.tsx | 24 + .../src/screens/home/components/hero/types.ts | 10 + .../src/screens/home/components/index.ts | 8 + .../__snapshots__/index.test.tsx.snap | 219 +++++++ .../home/components/tokenomics/hooks.ts | 49 ++ .../home/components/tokenomics/index.test.tsx | 87 +++ .../home/components/tokenomics/index.tsx | 98 ++++ .../home/components/tokenomics/queries.ts | 6 + .../home/components/tokenomics/styles.ts | 73 +++ .../home/components/tokenomics/utils.ts | 6 + .../__snapshots__/index.test.tsx.snap | 170 ++++++ .../desktop/__snapshots__/index.test.tsx.snap | 401 +++++++++++++ .../components/desktop/index.test.tsx | 43 ++ .../transactions/components/desktop/index.tsx | 123 ++++ .../transactions/components/desktop/styles.ts | 17 + .../transactions/components/desktop/utils.tsx | 28 + .../transactions/components/index.ts | 4 + .../mobile/__snapshots__/index.test.tsx.snap | 47 ++ .../components/mobile/index.test.tsx | 46 ++ .../transactions/components/mobile/index.tsx | 57 ++ .../home/components/transactions/hooks.ts | 53 ++ .../components/transactions/index.test.tsx | 86 +++ .../home/components/transactions/index.tsx | 52 ++ .../home/components/transactions/styles.ts | 31 + .../home/components/transactions/types.ts | 13 + .../src/screens/home/index.test.tsx | 74 +++ apps/web-stratos/src/screens/home/index.tsx | 25 + apps/web-stratos/src/screens/home/styles.ts | 78 +++ package.json | 3 +- packages/ui/public/locales/en/common.json | 3 +- packages/ui/public/locales/en/home.json | 8 +- packages/ui/public/locales/it/common.json | 3 +- packages/ui/public/locales/pl/common.json | 3 +- packages/ui/public/locales/zhs/common.json | 3 +- packages/ui/public/locales/zht/common.json | 3 +- .../mobile/__snapshots__/index.test.tsx.snap | 2 +- packages/ui/src/graphql/useApollo/index.ts | 2 +- .../src/hooks/useBigDipperNetworks/mocks.ts | 4 +- packages/ui/src/recoil/market/hooks.ts | 2 +- packages/ui/src/recoil/market/types.ts | 1 + turbo.json | 2 + yarn.lock | 506 +++++++++++++++- 103 files changed, 7320 insertions(+), 22 deletions(-) create mode 100644 apps/web-stratos/src/components/nav/components/title_bar/index.tsx create mode 100644 apps/web-stratos/src/components/nav/components/title_bar/utils.tsx create mode 100644 apps/web-stratos/src/pages/withGetServerSideProps.ts create mode 100644 apps/web-stratos/src/pages/withGetStaticProps.ts create mode 100644 apps/web-stratos/src/screens/app/__snapshots__/index.test.tsx.snap create mode 100644 apps/web-stratos/src/screens/app/components/index.ts create mode 100644 apps/web-stratos/src/screens/app/components/inner_app/__snapshots__/index.test.tsx.snap create mode 100644 apps/web-stratos/src/screens/app/components/inner_app/hooks.test.tsx create mode 100644 apps/web-stratos/src/screens/app/components/inner_app/hooks.ts create mode 100644 apps/web-stratos/src/screens/app/components/inner_app/index.test.tsx create mode 100644 apps/web-stratos/src/screens/app/components/inner_app/index.tsx create mode 100644 apps/web-stratos/src/screens/app/components/main/hooks.ts create mode 100644 apps/web-stratos/src/screens/app/components/main/index.tsx create mode 100644 apps/web-stratos/src/screens/app/components/recoil_debugger/index.tsx create mode 100644 apps/web-stratos/src/screens/app/hooks.ts create mode 100644 apps/web-stratos/src/screens/app/index.test.tsx create mode 100644 apps/web-stratos/src/screens/app/index.tsx create mode 100644 apps/web-stratos/src/screens/app/utils.tsx create mode 100644 apps/web-stratos/src/screens/home/components/blocks/__snapshots__/index.test.tsx.snap create mode 100644 apps/web-stratos/src/screens/home/components/blocks/components/desktop/__snapshots__/index.test.tsx.snap create mode 100644 apps/web-stratos/src/screens/home/components/blocks/components/desktop/index.test.tsx create mode 100644 apps/web-stratos/src/screens/home/components/blocks/components/desktop/index.tsx create mode 100644 apps/web-stratos/src/screens/home/components/blocks/components/desktop/styles.ts create mode 100644 apps/web-stratos/src/screens/home/components/blocks/components/desktop/utils.tsx create mode 100644 apps/web-stratos/src/screens/home/components/blocks/components/index.ts create mode 100644 apps/web-stratos/src/screens/home/components/blocks/components/mobile/__snapshots__/index.test.tsx.snap create mode 100644 apps/web-stratos/src/screens/home/components/blocks/components/mobile/index.test.tsx create mode 100644 apps/web-stratos/src/screens/home/components/blocks/components/mobile/index.tsx create mode 100644 apps/web-stratos/src/screens/home/components/blocks/hooks.ts create mode 100644 apps/web-stratos/src/screens/home/components/blocks/index.test.tsx create mode 100644 apps/web-stratos/src/screens/home/components/blocks/index.tsx create mode 100644 apps/web-stratos/src/screens/home/components/blocks/styles.ts create mode 100644 apps/web-stratos/src/screens/home/components/blocks/types.ts create mode 100644 apps/web-stratos/src/screens/home/components/charts/TokenomicChart.tsx create mode 100644 apps/web-stratos/src/screens/home/components/consensus/hooks.ts create mode 100644 apps/web-stratos/src/screens/home/components/consensus/index.tsx create mode 100644 apps/web-stratos/src/screens/home/components/consensus/styles.ts create mode 100644 apps/web-stratos/src/screens/home/components/data_blocks/__snapshots__/index.test.tsx.snap create mode 100644 apps/web-stratos/src/screens/home/components/data_blocks/components/index.ts create mode 100644 apps/web-stratos/src/screens/home/components/data_blocks/components/single_block/__snapshots__/index.test.tsx.snap create mode 100644 apps/web-stratos/src/screens/home/components/data_blocks/components/single_block/index.test.tsx create mode 100644 apps/web-stratos/src/screens/home/components/data_blocks/components/single_block/index.tsx create mode 100644 apps/web-stratos/src/screens/home/components/data_blocks/components/single_block/styles.ts create mode 100644 apps/web-stratos/src/screens/home/components/data_blocks/hooks.ts create mode 100644 apps/web-stratos/src/screens/home/components/data_blocks/index.test.tsx create mode 100644 apps/web-stratos/src/screens/home/components/data_blocks/index.tsx create mode 100644 apps/web-stratos/src/screens/home/components/data_blocks/styles.ts create mode 100644 apps/web-stratos/src/screens/home/components/hero/components/index.ts create mode 100644 apps/web-stratos/src/screens/home/components/hero/components/online_voting_power/__snapshots__/index.test.tsx.snap create mode 100644 apps/web-stratos/src/screens/home/components/hero/components/online_voting_power/hooks.ts create mode 100644 apps/web-stratos/src/screens/home/components/hero/components/online_voting_power/index.test.tsx create mode 100644 apps/web-stratos/src/screens/home/components/hero/components/online_voting_power/index.tsx create mode 100644 apps/web-stratos/src/screens/home/components/hero/components/online_voting_power/styles.ts create mode 100644 apps/web-stratos/src/screens/home/components/hero/components/token_price/hooks.ts create mode 100644 apps/web-stratos/src/screens/home/components/hero/components/token_price/index.tsx create mode 100644 apps/web-stratos/src/screens/home/components/hero/components/token_price/styles.ts create mode 100644 apps/web-stratos/src/screens/home/components/hero/hooks.ts create mode 100644 apps/web-stratos/src/screens/home/components/hero/index.tsx create mode 100644 apps/web-stratos/src/screens/home/components/hero/types.ts create mode 100644 apps/web-stratos/src/screens/home/components/index.ts create mode 100644 apps/web-stratos/src/screens/home/components/tokenomics/__snapshots__/index.test.tsx.snap create mode 100644 apps/web-stratos/src/screens/home/components/tokenomics/hooks.ts create mode 100644 apps/web-stratos/src/screens/home/components/tokenomics/index.test.tsx create mode 100644 apps/web-stratos/src/screens/home/components/tokenomics/index.tsx create mode 100644 apps/web-stratos/src/screens/home/components/tokenomics/queries.ts create mode 100644 apps/web-stratos/src/screens/home/components/tokenomics/styles.ts create mode 100644 apps/web-stratos/src/screens/home/components/tokenomics/utils.ts create mode 100644 apps/web-stratos/src/screens/home/components/transactions/__snapshots__/index.test.tsx.snap create mode 100644 apps/web-stratos/src/screens/home/components/transactions/components/desktop/__snapshots__/index.test.tsx.snap create mode 100644 apps/web-stratos/src/screens/home/components/transactions/components/desktop/index.test.tsx create mode 100644 apps/web-stratos/src/screens/home/components/transactions/components/desktop/index.tsx create mode 100644 apps/web-stratos/src/screens/home/components/transactions/components/desktop/styles.ts create mode 100644 apps/web-stratos/src/screens/home/components/transactions/components/desktop/utils.tsx create mode 100644 apps/web-stratos/src/screens/home/components/transactions/components/index.ts create mode 100644 apps/web-stratos/src/screens/home/components/transactions/components/mobile/__snapshots__/index.test.tsx.snap create mode 100644 apps/web-stratos/src/screens/home/components/transactions/components/mobile/index.test.tsx create mode 100644 apps/web-stratos/src/screens/home/components/transactions/components/mobile/index.tsx create mode 100644 apps/web-stratos/src/screens/home/components/transactions/hooks.ts create mode 100644 apps/web-stratos/src/screens/home/components/transactions/index.test.tsx create mode 100644 apps/web-stratos/src/screens/home/components/transactions/index.tsx create mode 100644 apps/web-stratos/src/screens/home/components/transactions/styles.ts create mode 100644 apps/web-stratos/src/screens/home/components/transactions/types.ts create mode 100644 apps/web-stratos/src/screens/home/index.test.tsx create mode 100644 apps/web-stratos/src/screens/home/index.tsx create mode 100644 apps/web-stratos/src/screens/home/styles.ts diff --git a/.pnp.cjs b/.pnp.cjs index 2218471549..0b172616d7 100755 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -3794,6 +3794,101 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ + ["@ethersproject/abi", [\ + ["npm:5.7.0", {\ + "packageLocation": "./.yarn/cache/@ethersproject-abi-npm-5.7.0-fdd80304df-bc6962bb6c.zip/node_modules/@ethersproject/abi/",\ + "packageDependencies": [\ + ["@ethersproject/abi", "npm:5.7.0"],\ + ["@ethersproject/address", "npm:5.7.0"],\ + ["@ethersproject/bignumber", "npm:5.7.0"],\ + ["@ethersproject/bytes", "npm:5.7.0"],\ + ["@ethersproject/constants", "npm:5.7.0"],\ + ["@ethersproject/hash", "npm:5.7.0"],\ + ["@ethersproject/keccak256", "npm:5.7.0"],\ + ["@ethersproject/logger", "npm:5.7.0"],\ + ["@ethersproject/properties", "npm:5.7.0"],\ + ["@ethersproject/strings", "npm:5.7.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["@ethersproject/abstract-provider", [\ + ["npm:5.7.0", {\ + "packageLocation": "./.yarn/cache/@ethersproject-abstract-provider-npm-5.7.0-f94be4e0b0-74cf469624.zip/node_modules/@ethersproject/abstract-provider/",\ + "packageDependencies": [\ + ["@ethersproject/abstract-provider", "npm:5.7.0"],\ + ["@ethersproject/bignumber", "npm:5.7.0"],\ + ["@ethersproject/bytes", "npm:5.7.0"],\ + ["@ethersproject/logger", "npm:5.7.0"],\ + ["@ethersproject/networks", "npm:5.7.1"],\ + ["@ethersproject/properties", "npm:5.7.0"],\ + ["@ethersproject/transactions", "npm:5.7.0"],\ + ["@ethersproject/web", "npm:5.7.1"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["@ethersproject/abstract-signer", [\ + ["npm:5.7.0", {\ + "packageLocation": "./.yarn/cache/@ethersproject-abstract-signer-npm-5.7.0-f61d0a970e-a823dac9cf.zip/node_modules/@ethersproject/abstract-signer/",\ + "packageDependencies": [\ + ["@ethersproject/abstract-signer", "npm:5.7.0"],\ + ["@ethersproject/abstract-provider", "npm:5.7.0"],\ + ["@ethersproject/bignumber", "npm:5.7.0"],\ + ["@ethersproject/bytes", "npm:5.7.0"],\ + ["@ethersproject/logger", "npm:5.7.0"],\ + ["@ethersproject/properties", "npm:5.7.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["@ethersproject/address", [\ + ["npm:5.7.0", {\ + "packageLocation": "./.yarn/cache/@ethersproject-address-npm-5.7.0-d27f4f2b80-64ea5ebea9.zip/node_modules/@ethersproject/address/",\ + "packageDependencies": [\ + ["@ethersproject/address", "npm:5.7.0"],\ + ["@ethersproject/bignumber", "npm:5.7.0"],\ + ["@ethersproject/bytes", "npm:5.7.0"],\ + ["@ethersproject/keccak256", "npm:5.7.0"],\ + ["@ethersproject/logger", "npm:5.7.0"],\ + ["@ethersproject/rlp", "npm:5.7.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["@ethersproject/base64", [\ + ["npm:5.7.0", {\ + "packageLocation": "./.yarn/cache/@ethersproject-base64-npm-5.7.0-ddf99521e0-7dd5d734d6.zip/node_modules/@ethersproject/base64/",\ + "packageDependencies": [\ + ["@ethersproject/base64", "npm:5.7.0"],\ + ["@ethersproject/bytes", "npm:5.7.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["@ethersproject/basex", [\ + ["npm:5.7.0", {\ + "packageLocation": "./.yarn/cache/@ethersproject-basex-npm-5.7.0-78ae312582-326087b7e1.zip/node_modules/@ethersproject/basex/",\ + "packageDependencies": [\ + ["@ethersproject/basex", "npm:5.7.0"],\ + ["@ethersproject/bytes", "npm:5.7.0"],\ + ["@ethersproject/properties", "npm:5.7.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["@ethersproject/bignumber", [\ + ["npm:5.7.0", {\ + "packageLocation": "./.yarn/cache/@ethersproject-bignumber-npm-5.7.0-cd761880ac-8c9a134b76.zip/node_modules/@ethersproject/bignumber/",\ + "packageDependencies": [\ + ["@ethersproject/bignumber", "npm:5.7.0"],\ + ["@ethersproject/bytes", "npm:5.7.0"],\ + ["@ethersproject/logger", "npm:5.7.0"],\ + ["bn.js", "npm:5.2.1"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["@ethersproject/bytes", [\ ["npm:5.7.0", {\ "packageLocation": "./.yarn/cache/@ethersproject-bytes-npm-5.7.0-4454fe4cb0-66ad365cea.zip/node_modules/@ethersproject/bytes/",\ @@ -3804,6 +3899,96 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ + ["@ethersproject/constants", [\ + ["npm:5.7.0", {\ + "packageLocation": "./.yarn/cache/@ethersproject-constants-npm-5.7.0-24294ccfde-6d4b135574.zip/node_modules/@ethersproject/constants/",\ + "packageDependencies": [\ + ["@ethersproject/constants", "npm:5.7.0"],\ + ["@ethersproject/bignumber", "npm:5.7.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["@ethersproject/contracts", [\ + ["npm:5.7.0", {\ + "packageLocation": "./.yarn/cache/@ethersproject-contracts-npm-5.7.0-4d3730222f-6ccf1121cb.zip/node_modules/@ethersproject/contracts/",\ + "packageDependencies": [\ + ["@ethersproject/contracts", "npm:5.7.0"],\ + ["@ethersproject/abi", "npm:5.7.0"],\ + ["@ethersproject/abstract-provider", "npm:5.7.0"],\ + ["@ethersproject/abstract-signer", "npm:5.7.0"],\ + ["@ethersproject/address", "npm:5.7.0"],\ + ["@ethersproject/bignumber", "npm:5.7.0"],\ + ["@ethersproject/bytes", "npm:5.7.0"],\ + ["@ethersproject/constants", "npm:5.7.0"],\ + ["@ethersproject/logger", "npm:5.7.0"],\ + ["@ethersproject/properties", "npm:5.7.0"],\ + ["@ethersproject/transactions", "npm:5.7.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["@ethersproject/hash", [\ + ["npm:5.7.0", {\ + "packageLocation": "./.yarn/cache/@ethersproject-hash-npm-5.7.0-7c00366b4e-6e9fa8d14e.zip/node_modules/@ethersproject/hash/",\ + "packageDependencies": [\ + ["@ethersproject/hash", "npm:5.7.0"],\ + ["@ethersproject/abstract-signer", "npm:5.7.0"],\ + ["@ethersproject/address", "npm:5.7.0"],\ + ["@ethersproject/base64", "npm:5.7.0"],\ + ["@ethersproject/bignumber", "npm:5.7.0"],\ + ["@ethersproject/bytes", "npm:5.7.0"],\ + ["@ethersproject/keccak256", "npm:5.7.0"],\ + ["@ethersproject/logger", "npm:5.7.0"],\ + ["@ethersproject/properties", "npm:5.7.0"],\ + ["@ethersproject/strings", "npm:5.7.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["@ethersproject/hdnode", [\ + ["npm:5.7.0", {\ + "packageLocation": "./.yarn/cache/@ethersproject-hdnode-npm-5.7.0-c6d5c6aa1c-bfe5ca2d89.zip/node_modules/@ethersproject/hdnode/",\ + "packageDependencies": [\ + ["@ethersproject/hdnode", "npm:5.7.0"],\ + ["@ethersproject/abstract-signer", "npm:5.7.0"],\ + ["@ethersproject/basex", "npm:5.7.0"],\ + ["@ethersproject/bignumber", "npm:5.7.0"],\ + ["@ethersproject/bytes", "npm:5.7.0"],\ + ["@ethersproject/logger", "npm:5.7.0"],\ + ["@ethersproject/pbkdf2", "npm:5.7.0"],\ + ["@ethersproject/properties", "npm:5.7.0"],\ + ["@ethersproject/sha2", "npm:5.7.0"],\ + ["@ethersproject/signing-key", "npm:5.7.0"],\ + ["@ethersproject/strings", "npm:5.7.0"],\ + ["@ethersproject/transactions", "npm:5.7.0"],\ + ["@ethersproject/wordlists", "npm:5.7.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["@ethersproject/json-wallets", [\ + ["npm:5.7.0", {\ + "packageLocation": "./.yarn/cache/@ethersproject-json-wallets-npm-5.7.0-f73c4734e5-f583458d22.zip/node_modules/@ethersproject/json-wallets/",\ + "packageDependencies": [\ + ["@ethersproject/json-wallets", "npm:5.7.0"],\ + ["@ethersproject/abstract-signer", "npm:5.7.0"],\ + ["@ethersproject/address", "npm:5.7.0"],\ + ["@ethersproject/bytes", "npm:5.7.0"],\ + ["@ethersproject/hdnode", "npm:5.7.0"],\ + ["@ethersproject/keccak256", "npm:5.7.0"],\ + ["@ethersproject/logger", "npm:5.7.0"],\ + ["@ethersproject/pbkdf2", "npm:5.7.0"],\ + ["@ethersproject/properties", "npm:5.7.0"],\ + ["@ethersproject/random", "npm:5.7.0"],\ + ["@ethersproject/strings", "npm:5.7.0"],\ + ["@ethersproject/transactions", "npm:5.7.0"],\ + ["aes-js", "npm:3.0.0"],\ + ["scrypt-js", "npm:3.0.1"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["@ethersproject/keccak256", [\ ["npm:5.7.0", {\ "packageLocation": "./.yarn/cache/@ethersproject-keccak256-npm-5.7.0-be838547c4-ff70950d82.zip/node_modules/@ethersproject/keccak256/",\ @@ -3824,6 +4009,224 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ + ["@ethersproject/networks", [\ + ["npm:5.7.1", {\ + "packageLocation": "./.yarn/cache/@ethersproject-networks-npm-5.7.1-eb06956095-0339f31230.zip/node_modules/@ethersproject/networks/",\ + "packageDependencies": [\ + ["@ethersproject/networks", "npm:5.7.1"],\ + ["@ethersproject/logger", "npm:5.7.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["@ethersproject/pbkdf2", [\ + ["npm:5.7.0", {\ + "packageLocation": "./.yarn/cache/@ethersproject-pbkdf2-npm-5.7.0-5b8e51d4b9-b895adb9e3.zip/node_modules/@ethersproject/pbkdf2/",\ + "packageDependencies": [\ + ["@ethersproject/pbkdf2", "npm:5.7.0"],\ + ["@ethersproject/bytes", "npm:5.7.0"],\ + ["@ethersproject/sha2", "npm:5.7.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["@ethersproject/properties", [\ + ["npm:5.7.0", {\ + "packageLocation": "./.yarn/cache/@ethersproject-properties-npm-5.7.0-00a99c747b-6ab0ccf0c3.zip/node_modules/@ethersproject/properties/",\ + "packageDependencies": [\ + ["@ethersproject/properties", "npm:5.7.0"],\ + ["@ethersproject/logger", "npm:5.7.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["@ethersproject/providers", [\ + ["npm:5.7.2", {\ + "packageLocation": "./.yarn/cache/@ethersproject-providers-npm-5.7.2-88293ff29f-1754c731a5.zip/node_modules/@ethersproject/providers/",\ + "packageDependencies": [\ + ["@ethersproject/providers", "npm:5.7.2"],\ + ["@ethersproject/abstract-provider", "npm:5.7.0"],\ + ["@ethersproject/abstract-signer", "npm:5.7.0"],\ + ["@ethersproject/address", "npm:5.7.0"],\ + ["@ethersproject/base64", "npm:5.7.0"],\ + ["@ethersproject/basex", "npm:5.7.0"],\ + ["@ethersproject/bignumber", "npm:5.7.0"],\ + ["@ethersproject/bytes", "npm:5.7.0"],\ + ["@ethersproject/constants", "npm:5.7.0"],\ + ["@ethersproject/hash", "npm:5.7.0"],\ + ["@ethersproject/logger", "npm:5.7.0"],\ + ["@ethersproject/networks", "npm:5.7.1"],\ + ["@ethersproject/properties", "npm:5.7.0"],\ + ["@ethersproject/random", "npm:5.7.0"],\ + ["@ethersproject/rlp", "npm:5.7.0"],\ + ["@ethersproject/sha2", "npm:5.7.0"],\ + ["@ethersproject/strings", "npm:5.7.0"],\ + ["@ethersproject/transactions", "npm:5.7.0"],\ + ["@ethersproject/web", "npm:5.7.1"],\ + ["bech32", "npm:1.1.4"],\ + ["ws", "virtual:88293ff29fa54efecc98d655f7a7551b282025b3465bc23ca5bb7a89a31c17930a7319e98225cf138bf4e6ccead5b30ae3c800738697b87af3441226d65f7ee3#npm:7.4.6"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["@ethersproject/random", [\ + ["npm:5.7.0", {\ + "packageLocation": "./.yarn/cache/@ethersproject-random-npm-5.7.0-a0466f53f9-017829c91c.zip/node_modules/@ethersproject/random/",\ + "packageDependencies": [\ + ["@ethersproject/random", "npm:5.7.0"],\ + ["@ethersproject/bytes", "npm:5.7.0"],\ + ["@ethersproject/logger", "npm:5.7.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["@ethersproject/rlp", [\ + ["npm:5.7.0", {\ + "packageLocation": "./.yarn/cache/@ethersproject-rlp-npm-5.7.0-a6c9e763ff-bce165b0f7.zip/node_modules/@ethersproject/rlp/",\ + "packageDependencies": [\ + ["@ethersproject/rlp", "npm:5.7.0"],\ + ["@ethersproject/bytes", "npm:5.7.0"],\ + ["@ethersproject/logger", "npm:5.7.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["@ethersproject/sha2", [\ + ["npm:5.7.0", {\ + "packageLocation": "./.yarn/cache/@ethersproject-sha2-npm-5.7.0-569c8bdff0-09321057c0.zip/node_modules/@ethersproject/sha2/",\ + "packageDependencies": [\ + ["@ethersproject/sha2", "npm:5.7.0"],\ + ["@ethersproject/bytes", "npm:5.7.0"],\ + ["@ethersproject/logger", "npm:5.7.0"],\ + ["hash.js", "npm:1.1.7"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["@ethersproject/signing-key", [\ + ["npm:5.7.0", {\ + "packageLocation": "./.yarn/cache/@ethersproject-signing-key-npm-5.7.0-51cfa7708e-8f8de09b0a.zip/node_modules/@ethersproject/signing-key/",\ + "packageDependencies": [\ + ["@ethersproject/signing-key", "npm:5.7.0"],\ + ["@ethersproject/bytes", "npm:5.7.0"],\ + ["@ethersproject/logger", "npm:5.7.0"],\ + ["@ethersproject/properties", "npm:5.7.0"],\ + ["bn.js", "npm:5.2.1"],\ + ["elliptic", "npm:6.5.4"],\ + ["hash.js", "npm:1.1.7"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["@ethersproject/solidity", [\ + ["npm:5.7.0", {\ + "packageLocation": "./.yarn/cache/@ethersproject-solidity-npm-5.7.0-75baa4e133-9a02f37f80.zip/node_modules/@ethersproject/solidity/",\ + "packageDependencies": [\ + ["@ethersproject/solidity", "npm:5.7.0"],\ + ["@ethersproject/bignumber", "npm:5.7.0"],\ + ["@ethersproject/bytes", "npm:5.7.0"],\ + ["@ethersproject/keccak256", "npm:5.7.0"],\ + ["@ethersproject/logger", "npm:5.7.0"],\ + ["@ethersproject/sha2", "npm:5.7.0"],\ + ["@ethersproject/strings", "npm:5.7.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["@ethersproject/strings", [\ + ["npm:5.7.0", {\ + "packageLocation": "./.yarn/cache/@ethersproject-strings-npm-5.7.0-efcb671e56-5ff78693ae.zip/node_modules/@ethersproject/strings/",\ + "packageDependencies": [\ + ["@ethersproject/strings", "npm:5.7.0"],\ + ["@ethersproject/bytes", "npm:5.7.0"],\ + ["@ethersproject/constants", "npm:5.7.0"],\ + ["@ethersproject/logger", "npm:5.7.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["@ethersproject/transactions", [\ + ["npm:5.7.0", {\ + "packageLocation": "./.yarn/cache/@ethersproject-transactions-npm-5.7.0-9a32c9e61a-a31b71996d.zip/node_modules/@ethersproject/transactions/",\ + "packageDependencies": [\ + ["@ethersproject/transactions", "npm:5.7.0"],\ + ["@ethersproject/address", "npm:5.7.0"],\ + ["@ethersproject/bignumber", "npm:5.7.0"],\ + ["@ethersproject/bytes", "npm:5.7.0"],\ + ["@ethersproject/constants", "npm:5.7.0"],\ + ["@ethersproject/keccak256", "npm:5.7.0"],\ + ["@ethersproject/logger", "npm:5.7.0"],\ + ["@ethersproject/properties", "npm:5.7.0"],\ + ["@ethersproject/rlp", "npm:5.7.0"],\ + ["@ethersproject/signing-key", "npm:5.7.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["@ethersproject/units", [\ + ["npm:5.7.0", {\ + "packageLocation": "./.yarn/cache/@ethersproject-units-npm-5.7.0-1c1f5ec7ca-304714f848.zip/node_modules/@ethersproject/units/",\ + "packageDependencies": [\ + ["@ethersproject/units", "npm:5.7.0"],\ + ["@ethersproject/bignumber", "npm:5.7.0"],\ + ["@ethersproject/constants", "npm:5.7.0"],\ + ["@ethersproject/logger", "npm:5.7.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["@ethersproject/wallet", [\ + ["npm:5.7.0", {\ + "packageLocation": "./.yarn/cache/@ethersproject-wallet-npm-5.7.0-f553c726b7-a4009bf733.zip/node_modules/@ethersproject/wallet/",\ + "packageDependencies": [\ + ["@ethersproject/wallet", "npm:5.7.0"],\ + ["@ethersproject/abstract-provider", "npm:5.7.0"],\ + ["@ethersproject/abstract-signer", "npm:5.7.0"],\ + ["@ethersproject/address", "npm:5.7.0"],\ + ["@ethersproject/bignumber", "npm:5.7.0"],\ + ["@ethersproject/bytes", "npm:5.7.0"],\ + ["@ethersproject/hash", "npm:5.7.0"],\ + ["@ethersproject/hdnode", "npm:5.7.0"],\ + ["@ethersproject/json-wallets", "npm:5.7.0"],\ + ["@ethersproject/keccak256", "npm:5.7.0"],\ + ["@ethersproject/logger", "npm:5.7.0"],\ + ["@ethersproject/properties", "npm:5.7.0"],\ + ["@ethersproject/random", "npm:5.7.0"],\ + ["@ethersproject/signing-key", "npm:5.7.0"],\ + ["@ethersproject/transactions", "npm:5.7.0"],\ + ["@ethersproject/wordlists", "npm:5.7.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["@ethersproject/web", [\ + ["npm:5.7.1", {\ + "packageLocation": "./.yarn/cache/@ethersproject-web-npm-5.7.1-dabb653410-7028c47103.zip/node_modules/@ethersproject/web/",\ + "packageDependencies": [\ + ["@ethersproject/web", "npm:5.7.1"],\ + ["@ethersproject/base64", "npm:5.7.0"],\ + ["@ethersproject/bytes", "npm:5.7.0"],\ + ["@ethersproject/logger", "npm:5.7.0"],\ + ["@ethersproject/properties", "npm:5.7.0"],\ + ["@ethersproject/strings", "npm:5.7.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["@ethersproject/wordlists", [\ + ["npm:5.7.0", {\ + "packageLocation": "./.yarn/cache/@ethersproject-wordlists-npm-5.7.0-00d314bfdb-30eb6eb073.zip/node_modules/@ethersproject/wordlists/",\ + "packageDependencies": [\ + ["@ethersproject/wordlists", "npm:5.7.0"],\ + ["@ethersproject/bytes", "npm:5.7.0"],\ + ["@ethersproject/hash", "npm:5.7.0"],\ + ["@ethersproject/logger", "npm:5.7.0"],\ + ["@ethersproject/properties", "npm:5.7.0"],\ + ["@ethersproject/strings", "npm:5.7.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["@gar/promisify", [\ ["npm:1.1.3", {\ "packageLocation": "./.yarn/cache/@gar-promisify-npm-1.1.3-ac1a325862-4059f790e2.zip/node_modules/@gar/promisify/",\ @@ -6935,6 +7338,46 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ + ["@tanstack/query-core", [\ + ["npm:5.0.0", {\ + "packageLocation": "./.yarn/cache/@tanstack-query-core-npm-5.0.0-843c45e740-19ef7a26d1.zip/node_modules/@tanstack/query-core/",\ + "packageDependencies": [\ + ["@tanstack/query-core", "npm:5.0.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["@tanstack/react-query", [\ + ["npm:5.0.0", {\ + "packageLocation": "./.yarn/cache/@tanstack-react-query-npm-5.0.0-d42943f0ae-266cdf25aa.zip/node_modules/@tanstack/react-query/",\ + "packageDependencies": [\ + ["@tanstack/react-query", "npm:5.0.0"]\ + ],\ + "linkType": "SOFT"\ + }],\ + ["virtual:0b0af0626773c617e43dd84ca20e9cd3d5694742b8a1890140127a1c7d9f4cad19c9eb9d31bc5cf3e25a810a1ced2fbd8f1e7f2985a160c27ef2ca70681d58a8#npm:5.0.0", {\ + "packageLocation": "./.yarn/__virtual__/@tanstack-react-query-virtual-38623111d4/0/cache/@tanstack-react-query-npm-5.0.0-d42943f0ae-266cdf25aa.zip/node_modules/@tanstack/react-query/",\ + "packageDependencies": [\ + ["@tanstack/react-query", "virtual:0b0af0626773c617e43dd84ca20e9cd3d5694742b8a1890140127a1c7d9f4cad19c9eb9d31bc5cf3e25a810a1ced2fbd8f1e7f2985a160c27ef2ca70681d58a8#npm:5.0.0"],\ + ["@tanstack/query-core", "npm:5.0.0"],\ + ["@types/react", "npm:18.2.6"],\ + ["@types/react-dom", "npm:18.2.4"],\ + ["@types/react-native", null],\ + ["react", "npm:18.2.0"],\ + ["react-dom", "virtual:9dce388d82c018b4a7af5edc7243e51f7023d1ab93a923b3959d0066ac6881c93b965a0932486426cbefa96c9c8e47849d5ff541d2404b8aca246480fa32f0d2#npm:18.2.0"],\ + ["react-native", null]\ + ],\ + "packagePeers": [\ + "@types/react-dom",\ + "@types/react-native",\ + "@types/react",\ + "react-dom",\ + "react-native",\ + "react"\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["@testing-library/dom", [\ ["npm:9.2.0", {\ "packageLocation": "./.yarn/cache/@testing-library-dom-npm-9.2.0-8830c42fff-b145f43cd0.zip/node_modules/@testing-library/dom/",\ @@ -8520,6 +8963,13 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["aes-js", [\ + ["npm:3.0.0", {\ + "packageLocation": "./.yarn/cache/aes-js-npm-3.0.0-fdf135c6be-251e26d533.zip/node_modules/aes-js/",\ + "packageDependencies": [\ + ["aes-js", "npm:3.0.0"]\ + ],\ + "linkType": "HARD"\ + }],\ ["npm:3.1.2", {\ "packageLocation": "./.yarn/cache/aes-js-npm-3.1.2-d7549a23a2-062154d50b.zip/node_modules/aes-js/",\ "packageDependencies": [\ @@ -8933,6 +9383,16 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["form-data", "npm:4.0.0"]\ ],\ "linkType": "HARD"\ + }],\ + ["npm:1.5.1", {\ + "packageLocation": "./.yarn/cache/axios-npm-1.5.1-6bc68e7d25-4444f06601.zip/node_modules/axios/",\ + "packageDependencies": [\ + ["axios", "npm:1.5.1"],\ + ["follow-redirects", "virtual:6bc68e7d25b7055a51c269c1fa5648c8246286db1b7949168396e2ffbde7ed58cf742783c99b4e376ba9a3e96fcc4d029f96bd1b532b606b5055545461060ec6#npm:1.15.3"],\ + ["form-data", "npm:4.0.0"],\ + ["proxy-from-env", "npm:1.1.0"]\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["axobject-query", [\ @@ -12376,6 +12836,45 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ + ["ethers", [\ + ["npm:5.7.2", {\ + "packageLocation": "./.yarn/cache/ethers-npm-5.7.2-eba7d781ee-b7c08cf3e2.zip/node_modules/ethers/",\ + "packageDependencies": [\ + ["ethers", "npm:5.7.2"],\ + ["@ethersproject/abi", "npm:5.7.0"],\ + ["@ethersproject/abstract-provider", "npm:5.7.0"],\ + ["@ethersproject/abstract-signer", "npm:5.7.0"],\ + ["@ethersproject/address", "npm:5.7.0"],\ + ["@ethersproject/base64", "npm:5.7.0"],\ + ["@ethersproject/basex", "npm:5.7.0"],\ + ["@ethersproject/bignumber", "npm:5.7.0"],\ + ["@ethersproject/bytes", "npm:5.7.0"],\ + ["@ethersproject/constants", "npm:5.7.0"],\ + ["@ethersproject/contracts", "npm:5.7.0"],\ + ["@ethersproject/hash", "npm:5.7.0"],\ + ["@ethersproject/hdnode", "npm:5.7.0"],\ + ["@ethersproject/json-wallets", "npm:5.7.0"],\ + ["@ethersproject/keccak256", "npm:5.7.0"],\ + ["@ethersproject/logger", "npm:5.7.0"],\ + ["@ethersproject/networks", "npm:5.7.1"],\ + ["@ethersproject/pbkdf2", "npm:5.7.0"],\ + ["@ethersproject/properties", "npm:5.7.0"],\ + ["@ethersproject/providers", "npm:5.7.2"],\ + ["@ethersproject/random", "npm:5.7.0"],\ + ["@ethersproject/rlp", "npm:5.7.0"],\ + ["@ethersproject/sha2", "npm:5.7.0"],\ + ["@ethersproject/signing-key", "npm:5.7.0"],\ + ["@ethersproject/solidity", "npm:5.7.0"],\ + ["@ethersproject/strings", "npm:5.7.0"],\ + ["@ethersproject/transactions", "npm:5.7.0"],\ + ["@ethersproject/units", "npm:5.7.0"],\ + ["@ethersproject/wallet", "npm:5.7.0"],\ + ["@ethersproject/web", "npm:5.7.1"],\ + ["@ethersproject/wordlists", "npm:5.7.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["event-target-shim", [\ ["npm:5.0.1", {\ "packageLocation": "./.yarn/cache/event-target-shim-npm-5.0.1-cb48709025-1ffe3bb22a.zip/node_modules/event-target-shim/",\ @@ -12735,6 +13234,26 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ],\ "linkType": "SOFT"\ }],\ + ["npm:1.15.3", {\ + "packageLocation": "./.yarn/cache/follow-redirects-npm-1.15.3-ca69c47b72-584da22ec5.zip/node_modules/follow-redirects/",\ + "packageDependencies": [\ + ["follow-redirects", "npm:1.15.3"]\ + ],\ + "linkType": "SOFT"\ + }],\ + ["virtual:6bc68e7d25b7055a51c269c1fa5648c8246286db1b7949168396e2ffbde7ed58cf742783c99b4e376ba9a3e96fcc4d029f96bd1b532b606b5055545461060ec6#npm:1.15.3", {\ + "packageLocation": "./.yarn/__virtual__/follow-redirects-virtual-ddb0bfe676/0/cache/follow-redirects-npm-1.15.3-ca69c47b72-584da22ec5.zip/node_modules/follow-redirects/",\ + "packageDependencies": [\ + ["follow-redirects", "virtual:6bc68e7d25b7055a51c269c1fa5648c8246286db1b7949168396e2ffbde7ed58cf742783c99b4e376ba9a3e96fcc4d029f96bd1b532b606b5055545461060ec6#npm:1.15.3"],\ + ["@types/debug", null],\ + ["debug", null]\ + ],\ + "packagePeers": [\ + "@types/debug",\ + "debug"\ + ],\ + "linkType": "HARD"\ + }],\ ["virtual:e278873748b4e9d158db595d2f6cb0351c74052f52b53924aec4d9299e7b6babfb62aead374cf2f6e453bc8bea3b052380dc6b1137a162bbfc102bbb1c8cac42#npm:1.15.2", {\ "packageLocation": "./.yarn/__virtual__/follow-redirects-virtual-a3e7f4d39a/0/cache/follow-redirects-npm-1.15.2-1ec1dd82be-faa66059b6.zip/node_modules/follow-redirects/",\ "packageDependencies": [\ @@ -18660,6 +19179,15 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ + ["scrypt-js", [\ + ["npm:3.0.1", {\ + "packageLocation": "./.yarn/cache/scrypt-js-npm-3.0.1-fd2d3fa606-b7c7d1a68d.zip/node_modules/scrypt-js/",\ + "packageDependencies": [\ + ["scrypt-js", "npm:3.0.1"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["scuid", [\ ["npm:1.1.0", {\ "packageLocation": "./.yarn/cache/scuid-npm-1.1.0-b68eab8aab-cd094ac371.zip/node_modules/scuid/",\ @@ -20868,6 +21396,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@next/eslint-plugin-next", "npm:13.4.1"],\ ["@socialgouv/matomo-next", "virtual:9dce388d82c018b4a7af5edc7243e51f7023d1ab93a923b3959d0066ac6881c93b965a0932486426cbefa96c9c8e47849d5ff541d2404b8aca246480fa32f0d2#npm:1.6.1"],\ ["@svgr/webpack", "npm:7.0.0"],\ + ["@tanstack/react-query", "virtual:0b0af0626773c617e43dd84ca20e9cd3d5694742b8a1890140127a1c7d9f4cad19c9eb9d31bc5cf3e25a810a1ced2fbd8f1e7f2985a160c27ef2ca70681d58a8#npm:5.0.0"],\ ["@testing-library/jest-dom", "npm:5.16.5"],\ ["@testing-library/react", "virtual:9dce388d82c018b4a7af5edc7243e51f7023d1ab93a923b3959d0066ac6881c93b965a0932486426cbefa96c9c8e47849d5ff541d2404b8aca246480fa32f0d2#npm:14.0.0"],\ ["@types/big.js", "npm:6.1.6"],\ @@ -20892,6 +21421,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@walletconnect/encoding", "npm:1.0.2"],\ ["@yarnpkg/pnpify", "npm:4.0.0-rc.43"],\ ["apollo-link-rest", "virtual:9dce388d82c018b4a7af5edc7243e51f7023d1ab93a923b3959d0066ac6881c93b965a0932486426cbefa96c9c8e47849d5ff541d2404b8aca246480fa32f0d2#npm:0.9.0"],\ + ["axios", "npm:1.5.1"],\ ["bech32", "npm:2.0.0"],\ ["big.js", "npm:6.2.1"],\ ["color", "npm:4.2.3"],\ @@ -20912,6 +21442,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["eslint-plugin-react-hooks", "virtual:680b866b1f607595b2b59758405cb64dff07d461aebb031486e5900d3b742457da054ab889d192b9338afd7b8895fdd9016de2dbb9d2a9b7e5b890949f153324#npm:4.6.0"],\ ["eslint-plugin-turbo", "virtual:680b866b1f607595b2b59758405cb64dff07d461aebb031486e5900d3b742457da054ab889d192b9338afd7b8895fdd9016de2dbb9d2a9b7e5b890949f153324#npm:1.9.3"],\ ["esprima", "npm:4.0.1"],\ + ["ethers", "npm:5.7.2"],\ ["framer-motion", "virtual:9dce388d82c018b4a7af5edc7243e51f7023d1ab93a923b3959d0066ac6881c93b965a0932486426cbefa96c9c8e47849d5ff541d2404b8aca246480fa32f0d2#npm:10.12.8"],\ ["graphql", "npm:16.6.0"],\ ["graphql-tag", "virtual:9dce388d82c018b4a7af5edc7243e51f7023d1ab93a923b3959d0066ac6881c93b965a0932486426cbefa96c9c8e47849d5ff541d2404b8aca246480fa32f0d2#npm:2.12.6"],\ @@ -21220,6 +21751,13 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["ws", [\ + ["npm:7.4.6", {\ + "packageLocation": "./.yarn/cache/ws-npm-7.4.6-9c9a725604-3a990b32ed.zip/node_modules/ws/",\ + "packageDependencies": [\ + ["ws", "npm:7.4.6"]\ + ],\ + "linkType": "SOFT"\ + }],\ ["npm:7.5.3", {\ "packageLocation": "./.yarn/cache/ws-npm-7.5.3-3a046a0b1a-423dc0d859.zip/node_modules/ws/",\ "packageDependencies": [\ @@ -21248,6 +21786,23 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ],\ "linkType": "SOFT"\ }],\ + ["virtual:88293ff29fa54efecc98d655f7a7551b282025b3465bc23ca5bb7a89a31c17930a7319e98225cf138bf4e6ccead5b30ae3c800738697b87af3441226d65f7ee3#npm:7.4.6", {\ + "packageLocation": "./.yarn/__virtual__/ws-virtual-e1e964a4e5/0/cache/ws-npm-7.4.6-9c9a725604-3a990b32ed.zip/node_modules/ws/",\ + "packageDependencies": [\ + ["ws", "virtual:88293ff29fa54efecc98d655f7a7551b282025b3465bc23ca5bb7a89a31c17930a7319e98225cf138bf4e6ccead5b30ae3c800738697b87af3441226d65f7ee3#npm:7.4.6"],\ + ["@types/bufferutil", null],\ + ["@types/utf-8-validate", null],\ + ["bufferutil", null],\ + ["utf-8-validate", null]\ + ],\ + "packagePeers": [\ + "@types/bufferutil",\ + "@types/utf-8-validate",\ + "bufferutil",\ + "utf-8-validate"\ + ],\ + "linkType": "HARD"\ + }],\ ["virtual:98e3caa468f78c8d1772297fdca2bb95c2f2ac62eba849cccf74d659866440d5f484c849bf4209670a28b43ae6333190e0cc19aa99c859b69eec0c5e48ddc600#npm:7.5.9", {\ "packageLocation": "./.yarn/__virtual__/ws-virtual-3c72b2594c/0/cache/ws-npm-7.5.9-26f12a5ed6-c3c100a181.zip/node_modules/ws/",\ "packageDependencies": [\ diff --git a/apps/web-multiversx/src/graphql/useApollo/index.ts b/apps/web-multiversx/src/graphql/useApollo/index.ts index c47c705e3f..59c18fb7f6 100644 --- a/apps/web-multiversx/src/graphql/useApollo/index.ts +++ b/apps/web-multiversx/src/graphql/useApollo/index.ts @@ -36,7 +36,7 @@ export function profileApi() { return 'https://gql.mainnet.desmos.network/v1/graphql'; } -export const BIG_DIPPER_NETWORKS = 'https://raw.githubusercontent.com/forbole/big-dipper-networks/main/'; +export const BIG_DIPPER_NETWORKS = 'https://raw.githubusercontent.com/stratosnet/big-dipper-networks/main/'; /** * It creates a new Apollo Client, and sets the default options for it diff --git a/apps/web-stratos/package.json b/apps/web-stratos/package.json index e02f7a1e40..b4188d8ac3 100644 --- a/apps/web-stratos/package.json +++ b/apps/web-stratos/package.json @@ -26,15 +26,18 @@ "@mui/icons-material": "^5.11.16", "@mui/material": "^5.12.3", "@socialgouv/matomo-next": "^1.6.1", + "@tanstack/react-query": "^5.0.0", "@walletconnect/client": "^1.8.0", "@walletconnect/encoding": "^1.0.2", "@yarnpkg/pnpify": "^4.0.0-rc.43", "apollo-link-rest": "^0.9.0", + "axios": "^1.5.1", "bech32": "^2.0.0", "big.js": "^6.2.1", "color": "^4.2.3", "copy-to-clipboard": "^3.3.3", "dayjs": "^1.11.7", + "ethers": "5.7.2", "framer-motion": "^10.12.8", "graphql": "^16.6.0", "graphql-ws": "^5.12.1", diff --git a/apps/web-stratos/src/chain.json b/apps/web-stratos/src/chain.json index f14e3f0f52..d0892f24ec 100644 --- a/apps/web-stratos/src/chain.json +++ b/apps/web-stratos/src/chain.json @@ -57,7 +57,9 @@ "tokenomics": { "one": "#43A1BE", "two": "#E3BB55", - "three": "#20D292" + "three": "#20D292", + "four": "#2070D2", + "five": "#C220D2" }, "condition": { "zero": "#E6E6E6", @@ -152,7 +154,9 @@ "tokenomics": { "one": "#2FB6E0", "two": "#FFC93D", - "three": "#20D494" + "three": "#20D494", + "four": "#2070D2", + "five": "#C220D2" }, "condition": { "zero": "#E6E6E6", diff --git a/apps/web-stratos/src/components/nav/components/title_bar/index.tsx b/apps/web-stratos/src/components/nav/components/title_bar/index.tsx new file mode 100644 index 0000000000..de43986231 --- /dev/null +++ b/apps/web-stratos/src/components/nav/components/title_bar/index.tsx @@ -0,0 +1,40 @@ +import Typography from '@mui/material/Typography'; +import useAppTranslation from '@/hooks/useAppTranslation'; +import { FC } from 'react'; +import { useRecoilValue } from 'recoil'; +import ChainIcon from '@/components/ChainIcon'; +import useStyles from '@/components/nav/components/title_bar/styles'; +import { useFormatMarket } from '@/components/nav/components/title_bar/utils'; +import { readMarket } from '@/recoil/market'; + +type TitleBarProps = { + className?: string; + title: string; +}; + +const TitleBar: FC = ({ className, title }) => { + const { t } = useAppTranslation('common'); + const { classes, cx } = useStyles(); + const marketState = useRecoilValue(readMarket); + + const market = useFormatMarket(marketState); + + return ( +
+ {!title && } + {!!title && {title}} +
+ {market.map((x) => ( +
+ + {t(x.key)} + + {x.data} +
+ ))} +
+
+ ); +}; + +export default TitleBar; diff --git a/apps/web-stratos/src/components/nav/components/title_bar/utils.tsx b/apps/web-stratos/src/components/nav/components/title_bar/utils.tsx new file mode 100644 index 0000000000..fd1797bc3b --- /dev/null +++ b/apps/web-stratos/src/components/nav/components/title_bar/utils.tsx @@ -0,0 +1,59 @@ +import numeral from 'numeral'; +import chainConfig from '@/chainConfig'; +import { type AtomState } from '@/recoil/market'; +import { formatNumber } from '@/utils/format_token'; +import { useTokenomics } from '@/screens/home/components/tokenomics/hooks'; +import { prettyFormat } from '@/screens/home/components/tokenomics/utils'; + +const { primaryTokenUnit, tokenUnits } = chainConfig(); + +export const useFormatMarket = (data: AtomState) => { + const { data: tokenomics } = useTokenomics(); + const exludedItems = [null, 0]; + const marketCap = exludedItems.includes(data.marketCap) + ? 'N/A' + : `$${formatNumber(data.marketCap?.toString() ?? '', 2)}`; + + const miningReward = tokenomics?.miningReward + ? `${prettyFormat(tokenomics?.miningReward, 18, 0)} ${tokenUnits[ + primaryTokenUnit + ].display.toUpperCase()}/epoch` + : 'N/A'; + + const apr = + tokenomics?.miningReward && tokenomics?.bonded + ? `${prettyFormat( + tokenomics?.miningReward + .mul(6 * 24 * 365) + .mul(1_000_000) + .div(tokenomics?.bonded), + 4, + 2 + )}%` + : 'N/A'; + + return [ + { + key: 'marketCap', + data: marketCap, + }, + { + key: 'miningReward', + data: miningReward, + }, + { + key: 'apr', + data: apr, + }, + { + key: 'supply', + data: `${formatNumber(data.supply.value, 2)} ${data.supply.displayDenom.toUpperCase()}`, + }, + { + key: 'communityPool', + data: `${numeral(data.communityPool.value).format( + '0,0.00' + )} ${data.communityPool.displayDenom.toUpperCase()}`, + }, + ]; +}; diff --git a/apps/web-stratos/src/pages/withGetServerSideProps.ts b/apps/web-stratos/src/pages/withGetServerSideProps.ts new file mode 100644 index 0000000000..a911623cff --- /dev/null +++ b/apps/web-stratos/src/pages/withGetServerSideProps.ts @@ -0,0 +1,18 @@ +import { GetServerSidePropsContext } from 'next'; +import { UserConfig } from 'next-i18next'; +import { serverSideTranslations } from 'next-i18next/serverSideTranslations'; + +function withGetServerSideProps(nextI18NextConfig: UserConfig, ...namespacesRequired: string[]) { + const namespaceForApp = process.env.NEXT_PUBLIC_APP_NAME ?? ''; + return async ({ locale }: GetServerSidePropsContext) => ({ + props: { + ...(await serverSideTranslations( + locale || 'en', + ['common', namespaceForApp, ...namespacesRequired], + nextI18NextConfig + )), + }, + }); +} + +export default withGetServerSideProps; diff --git a/apps/web-stratos/src/pages/withGetStaticProps.ts b/apps/web-stratos/src/pages/withGetStaticProps.ts new file mode 100644 index 0000000000..35e64a2a5a --- /dev/null +++ b/apps/web-stratos/src/pages/withGetStaticProps.ts @@ -0,0 +1,27 @@ +import { GetStaticPropsContext } from 'next'; +import { UserConfig } from 'next-i18next'; +import { serverSideTranslations } from 'next-i18next/serverSideTranslations'; +import { dehydrate, QueryClient } from '@tanstack/react-query'; +import { getMetrics } from '@/screens/home/components/tokenomics/queries'; + +function withGetStaticProps(nextI18NextConfig: UserConfig, ...namespacesRequired: string[]) { + const namespaceForApp = process.env.NEXT_PUBLIC_APP_NAME ?? ''; + + return async ({ locale }: GetStaticPropsContext) => { + const queryClient = new QueryClient(); + const dehydratedState = dehydrate(queryClient); + await queryClient.prefetchQuery({ queryKey: ['metrics'], queryFn: getMetrics }); + return { + props: { + dehydratedState, + ...(await serverSideTranslations( + locale || 'en', + ['common', namespaceForApp, ...namespacesRequired], + nextI18NextConfig + )), + }, + }; + }; +} + +export default withGetStaticProps; diff --git a/apps/web-stratos/src/screens/app/__snapshots__/index.test.tsx.snap b/apps/web-stratos/src/screens/app/__snapshots__/index.test.tsx.snap new file mode 100644 index 0000000000..5bf38ca51e --- /dev/null +++ b/apps/web-stratos/src/screens/app/__snapshots__/index.test.tsx.snap @@ -0,0 +1,235 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`screen: _app matches snapshot 1`] = ` +[ + .emotion-0 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: center; + -ms-flex-pack: center; + -webkit-justify-content: center; + justify-content: center; + min-height: 100vh; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + background: #F8F8F8; +} + +.emotion-0 a { + color: #4092CD; +} + +.emotion-1 { + width: 275px; + display: -webkit-inline-box; + display: -webkit-inline-flex; + display: -ms-inline-flexbox; + display: inline-flex; +} + +.emotion-2 { + width: auto; + height: auto; + max-width: 100%; + max-height: 100%; + min-width: 100%; + display: none; +} + +.mode-dark .emotion-2 { + display: initial; +} + +.emotion-3 { + width: auto; + height: auto; + max-width: 100%; + max-height: 100%; + min-width: 100%; + object-fit: contain; +} + +.mode-dark .emotion-3 { + display: none; +} + +.emotion-4 { + display: grid; + grid-template-columns: repeat(4, 65px); + gap: 8px; + margin: 24px 0px; +} + +.emotion-5 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: center; + -ms-flex-pack: center; + -webkit-justify-content: center; + justify-content: center; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; +} + +.emotion-5 .MuiTypography-h1 { + width: 100%; + text-align: center; + background: #FFFFFF; + padding: 16px; + margin-bottom: 16px; +} + +.emotion-6 { + margin: 0; + font-size: 2rem; + letter-spacing: 0.25px; + font-family: fontFamily; + font-weight: 300; + line-height: 1.167; +} + +.emotion-7 { + margin: 0; + font-size: 1.25rem; + letter-spacing: 0.15px; + font-family: fontFamily; + font-weight: 400; + line-height: 1.167; +} + +.emotion-17 { + margin: 0; + font-size: 1.5rem; + letter-spacing: 0; + font-family: fontFamily; + font-weight: 300; + line-height: 1.2; + color: #FF835B; +} + +
+ + logo + logo + +
+
+

+ 0 +

+

+ Day +

+
+
+

+ 0 +

+

+ Hour +

+
+
+

+ 0 +

+

+ Min +

+
+
+

+ 0 +

+

+ Sec +

+
+
+

+ desmos-mainnet +

+
, +
, +] +`; diff --git a/apps/web-stratos/src/screens/app/components/index.ts b/apps/web-stratos/src/screens/app/components/index.ts new file mode 100644 index 0000000000..777e912d0e --- /dev/null +++ b/apps/web-stratos/src/screens/app/components/index.ts @@ -0,0 +1,4 @@ +import InnerApp from '@/screens/app/components/inner_app'; +import Main from '@/screens/app/components/main'; + +export { InnerApp, Main }; diff --git a/apps/web-stratos/src/screens/app/components/inner_app/__snapshots__/index.test.tsx.snap b/apps/web-stratos/src/screens/app/components/inner_app/__snapshots__/index.test.tsx.snap new file mode 100644 index 0000000000..40697ad843 --- /dev/null +++ b/apps/web-stratos/src/screens/app/components/inner_app/__snapshots__/index.test.tsx.snap @@ -0,0 +1,7 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`screen: _app/InnerApp matches snapshot 1`] = ` +
+`; diff --git a/apps/web-stratos/src/screens/app/components/inner_app/hooks.test.tsx b/apps/web-stratos/src/screens/app/components/inner_app/hooks.test.tsx new file mode 100644 index 0000000000..8e804ac98a --- /dev/null +++ b/apps/web-stratos/src/screens/app/components/inner_app/hooks.test.tsx @@ -0,0 +1,21 @@ +import { useChainHealthCheck } from '@/screens/app/components/inner_app/hooks'; +import { mockClient } from '@/tests/mocks/mockApollo'; +import { ApolloProvider } from '@apollo/client'; +import { cleanup, renderHook } from '@testing-library/react'; + +jest.mock('react-toastify', () => ({ + toast: jest.fn(), +})); + +describe('hook: useChainHealthCheck', () => { + test('handles open correctly', () => { + renderHook(() => useChainHealthCheck(), { + wrapper: ({ children }) => {children}, + }); + }); +}); + +afterEach(() => { + cleanup(); + jest.clearAllMocks(); +}); diff --git a/apps/web-stratos/src/screens/app/components/inner_app/hooks.ts b/apps/web-stratos/src/screens/app/components/inner_app/hooks.ts new file mode 100644 index 0000000000..0e7147fbdc --- /dev/null +++ b/apps/web-stratos/src/screens/app/components/inner_app/hooks.ts @@ -0,0 +1,37 @@ +import useAppTranslation from '@/hooks/useAppTranslation'; +import { useEffect, useState } from 'react'; +import { toast } from 'react-toastify'; +import dayjs from '@/utils/dayjs'; +import { useLatestBlockTimestampLazyQuery } from '@/graphql/types/general_types'; + +const isClient = typeof window === 'object'; + +export function useChainHealthCheck() { + const [chainActive, setChainActive] = useState(true); + const { t } = useAppTranslation('common'); + const [getLatestBlockTimestamp] = useLatestBlockTimestampLazyQuery({ + onCompleted: (data) => { + const timestamp = dayjs.utc(data?.block?.[0]?.timestamp ?? ''); + const timeNow = dayjs.utc(); + const timeDifference = timeNow.diff(timestamp, 's'); + // if latest block has been over a minute ago + if (timeDifference > 60 && chainActive) { + toast.error( + t('blockTimeAgo', { + time: dayjs.utc(timestamp).fromNow(), + }), + { + autoClose: false, + } + ); + setChainActive(false); + } + }, + }); + + useEffect(() => { + if (!isClient) return; + getLatestBlockTimestamp(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); +} diff --git a/apps/web-stratos/src/screens/app/components/inner_app/index.test.tsx b/apps/web-stratos/src/screens/app/components/inner_app/index.test.tsx new file mode 100644 index 0000000000..89a4e82ec6 --- /dev/null +++ b/apps/web-stratos/src/screens/app/components/inner_app/index.test.tsx @@ -0,0 +1,42 @@ +import InnerApp from '@/screens/app/components/inner_app'; +import { mockClient } from '@/tests/mocks/mockApollo'; +import type { Router } from 'next/router'; +import renderer from 'react-test-renderer'; + +// ================================== +// global setup +// ================================== +let component: renderer.ReactTestRenderer; + +// ================================== +// mocks +// ================================== +jest.mock('@/screens/app/components/inner_app/hooks', () => ({ + useChainHealthCheck: () => mockClient, +})); + +// ================================== +// unit tests +// ================================== +describe('screen: _app/InnerApp', () => { + it('matches snapshot', () => { + renderer.act(() => { + component = renderer.create( +
} + pageProps={{ + props: 'props', + }} + /> + ); + }); + + const tree = component?.toJSON(); + expect(tree).toMatchSnapshot(); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); +}); diff --git a/apps/web-stratos/src/screens/app/components/inner_app/index.tsx b/apps/web-stratos/src/screens/app/components/inner_app/index.tsx new file mode 100644 index 0000000000..2700a2e7af --- /dev/null +++ b/apps/web-stratos/src/screens/app/components/inner_app/index.tsx @@ -0,0 +1,14 @@ +import { AnimatePresence } from 'framer-motion'; +import { AppProps } from 'next/app'; +import { useChainHealthCheck } from '@/screens/app/components/inner_app/hooks'; + +function InnerApp({ Component, pageProps }: AppProps) { + useChainHealthCheck(); + return ( + + ; + + ); +} + +export default InnerApp; diff --git a/apps/web-stratos/src/screens/app/components/main/hooks.ts b/apps/web-stratos/src/screens/app/components/main/hooks.ts new file mode 100644 index 0000000000..71e9da2e66 --- /dev/null +++ b/apps/web-stratos/src/screens/app/components/main/hooks.ts @@ -0,0 +1,30 @@ +import { createTheme } from '@mui/material/styles'; +import { useState } from 'react'; +import { useRecoilValue } from 'recoil'; +import chainConfig from '@/chainConfig'; +import { getThemeTemplate, readTheme } from '@/recoil/settings'; +import dayjs from '@/utils/dayjs'; + +const { genesis } = chainConfig(); + +export const useTheme = (fontFamily: string) => { + const theme = useRecoilValue(readTheme); + const muiTheme = createTheme(getThemeTemplate(theme, fontFamily)); + return { muiTheme }; +}; + +export const useGenesis = () => { + const utcTimeNow = dayjs.utc().format('YYYY-MM-DDTHH:mm:ss'); + const [genesisStarted, setGenesis] = useState(genesis.time < utcTimeNow); + + const startGenesis = () => { + setTimeout(() => { + setGenesis(true); + }, 10000); + }; + + return { + genesisStarted, + startGenesis, + }; +}; diff --git a/apps/web-stratos/src/screens/app/components/main/index.tsx b/apps/web-stratos/src/screens/app/components/main/index.tsx new file mode 100644 index 0000000000..912b5e68d0 --- /dev/null +++ b/apps/web-stratos/src/screens/app/components/main/index.tsx @@ -0,0 +1,105 @@ +import useBigDipperNetworks from '@/hooks/useBigDipperNetworks'; +import { useMarketRecoil } from '@/recoil/market'; +import { useSettingsRecoil } from '@/recoil/settings'; +import { useValidatorRecoil } from '@/recoil/validators/hooks'; +import { useUserRecoil } from '@/recoil/user'; +import { useWalletRecoil } from '@/recoil/wallet'; +import InnerApp from '@/screens/app/components/inner_app'; +import { useGenesis, useTheme } from '@/screens/app/components/main/hooks'; +import Countdown from '@/screens/countdown'; +import InitialLoad from '@/screens/initial_load'; +import createEmotionCache from '@/styles/createEmotionCache'; +import { CacheProvider, EmotionCache } from '@emotion/react'; +import CssBaseline from '@mui/material/CssBaseline'; +import { ThemeProvider } from '@mui/material/styles'; +import { AppProps } from 'next/app'; +import { Hind_Madurai } from 'next/font/google'; +import Head from 'next/head'; +import { useEffect, useState } from 'react'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { ToastContainer } from 'react-toastify'; + +const hindMadurai = Hind_Madurai({ + weight: '400', + style: 'normal', + display: 'swap', + preload: true, + subsets: ['latin', 'latin-ext', 'tamil'], +}); + +// Client-side cache, shared for the whole session of the user in the browser. +const clientSideEmotionCache = createEmotionCache(); + +export interface MainProps extends AppProps { + emotionCache?: EmotionCache; +} + +const Main = (props: MainProps) => { + const { emotionCache = clientSideEmotionCache } = props; + + const [queryClient] = useState(() => new QueryClient()); + + // ===================================== + // init recoil values + // ===================================== + useSettingsRecoil(); + useBigDipperNetworks(); + useMarketRecoil(); + useUserRecoil(); + useWalletRecoil(); + const { loading } = useValidatorRecoil(); + + // ===================================== + // general setup + // ===================================== + const { muiTheme } = useTheme(hindMadurai.style.fontFamily); + const { genesisStarted, startGenesis } = useGenesis(); + + let Component = null; + + if (!genesisStarted) { + Component = ; + } else { + Component = ( + <> + {loading && } + + + ); + } + + /* Adding a class to the document element to indicate the dark mode. */ + useEffect(() => { + if (typeof document !== 'undefined' && document?.documentElement) { + document.documentElement.classList.toggle('mode-dark', muiTheme.palette.mode === 'dark'); + document + .querySelector('meta[name="theme-color"]') + ?.setAttribute('content', muiTheme.palette.primary.main); + } + }, [muiTheme.palette.mode, muiTheme.palette.primary.main]); + + return ( + + + + + + + {Component} + + + + ); +}; + +export default Main; diff --git a/apps/web-stratos/src/screens/app/components/recoil_debugger/index.tsx b/apps/web-stratos/src/screens/app/components/recoil_debugger/index.tsx new file mode 100644 index 0000000000..1c118cd3f7 --- /dev/null +++ b/apps/web-stratos/src/screens/app/components/recoil_debugger/index.tsx @@ -0,0 +1,18 @@ +/* eslint-disable */ +import { useEffect } from 'react'; +import { useRecoilSnapshot } from 'recoil'; + +function DebugObserver() { + const snapshot = useRecoilSnapshot(); + + useEffect(() => { + console.log('The following atoms were modified:'); + for (const node of snapshot.getNodes_UNSTABLE({ isModified: true })) { + console.log(node.key, snapshot.getLoadable(node)); + } + }, [snapshot]); + + return null; +} + +export default DebugObserver; diff --git a/apps/web-stratos/src/screens/app/hooks.ts b/apps/web-stratos/src/screens/app/hooks.ts new file mode 100644 index 0000000000..8145012e17 --- /dev/null +++ b/apps/web-stratos/src/screens/app/hooks.ts @@ -0,0 +1,43 @@ +import chainConfig from '@/chainConfig'; +import { init } from '@socialgouv/matomo-next'; +import * as jdenticon from 'jdenticon'; +import useAppTranslation from '@/hooks/useAppTranslation'; +import { useEffect, useRef } from 'react'; + +const { chainType, marketing } = chainConfig(); +const matomoUrls = [process.env.NEXT_PUBLIC_MATOMO_URL, marketing.matomoURL]; +const matomoSiteIds = [process.env.NEXT_PUBLIC_MATOMO_SITE_ID, marketing.matomoSiteID]; + +export const useApp = () => { + // ========================== + // language + // ========================== + const { i18n } = useAppTranslation(); + const initializedRef = useRef(false); + + useEffect(() => { + if (initializedRef.current) return; + initializedRef.current = true; + + const url = matomoUrls.find((u) => u); + const siteId = matomoSiteIds.find((i) => i); + if (url && siteId && /^mainnet$/i.test(chainType)) init({ url, siteId }); + // jdenticon theme + jdenticon.configure({ + hues: [207], + lightness: { + color: [0.84, 0.84], + grayscale: [0.84, 0.84], + }, + saturation: { + color: 0.48, + grayscale: 0.48, + }, + backColor: '#2a4766', + }); + }, [i18n.language]); + + useEffect(() => { + document.cookie = `NEXT_LOCALE=${i18n.language}`; + }, [i18n.language]); +}; diff --git a/apps/web-stratos/src/screens/app/index.test.tsx b/apps/web-stratos/src/screens/app/index.test.tsx new file mode 100644 index 0000000000..13da2995ad --- /dev/null +++ b/apps/web-stratos/src/screens/app/index.test.tsx @@ -0,0 +1,30 @@ +import MyApp from '@/screens/app'; +import { mockClient } from '@/tests/mocks/mockApollo'; +import type { Router } from 'next/router'; +import renderer from 'react-test-renderer'; + +// ================================== +// mocks +// ================================== +jest.mock('@/graphql/useApollo', () => () => mockClient); + +jest.mock('@/screens/app/hooks', () => ({ + useApp: () => jest.fn(), +})); + +// ================================== +// unit tests +// ================================== +describe('screen: _app', () => { + it('matches snapshot', () => { + const component = renderer.create( +
} pageProps={{}} /> + ); + const tree = component?.toJSON(); + expect(tree).toMatchSnapshot(); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); +}); diff --git a/apps/web-stratos/src/screens/app/index.tsx b/apps/web-stratos/src/screens/app/index.tsx new file mode 100644 index 0000000000..31eab82015 --- /dev/null +++ b/apps/web-stratos/src/screens/app/index.tsx @@ -0,0 +1,49 @@ +import chainConfig from '@/chainConfig'; +import useApollo from '@/graphql/useApollo'; +import { useWindowOrigin } from '@/hooks/use_window'; +import Main, { MainProps } from '@/screens/app/components/main'; +import { useApp } from '@/screens/app/hooks'; +import { + ADDITIONAL_LINK_TAGS_SEO, + ADDITIONAL_META_TAGS, + OPEN_GRAPH_SEO, + TWITTER_SEO, +} from '@/screens/app/utils'; +import { ApolloProvider, NormalizedCacheObject } from '@apollo/client'; +import useAppTranslation from '@/hooks/useAppTranslation'; +import { DefaultSeo } from 'next-seo'; +import { RecoilRoot } from 'recoil'; + +const { title } = chainConfig(); + +function MyApp(props: MainProps<{ initialApolloState?: NormalizedCacheObject }>) { + useApp(); + const { pageProps } = props; + const apolloClient = useApollo(pageProps.initialApolloState); + const { t } = useAppTranslation(); + const { location } = useWindowOrigin(); + + return ( + + + +
+ + + ); +} + +export default MyApp; diff --git a/apps/web-stratos/src/screens/app/utils.tsx b/apps/web-stratos/src/screens/app/utils.tsx new file mode 100644 index 0000000000..c6a1a5316f --- /dev/null +++ b/apps/web-stratos/src/screens/app/utils.tsx @@ -0,0 +1,64 @@ +import chainConfig from '@/chainConfig'; + +const { basePath, previewImage } = chainConfig(); + +export const OPEN_GRAPH_SEO = { + type: 'website', + site_name: 'Big Dipper', + images: [ + { + url: previewImage ?? '', + width: 800, + height: 600, + alt: 'Preview Photo', + }, + ], +}; + +export const TWITTER_SEO = { + cardType: 'summary_large_image', +}; + +export const ADDITIONAL_LINK_TAGS_SEO = [ + { + rel: 'apple-touch-icon', + href: `${basePath}/icons/apple-touch-icon.png`, + sizes: '180x180', + }, + { + rel: 'icon', + type: 'image/png', + href: `${basePath}/icons/favicon-32x32.png`, + sizes: '32x32', + }, + { + rel: 'icon', + type: 'image/png', + href: `${basePath}/icons/favicon-16x16.png`, + sizes: '16x16', + }, + { + rel: 'manifest', + href: `${basePath}/icons/site.webmanifest`, + }, + { + rel: 'mask-icon', + href: `${basePath}/icons/safari-pinned-tab.svg`, + color: '#5bbad5', + }, + { + rel: 'shortcut icon', + href: `${basePath}/icons/favicon.ico`, + }, +]; + +export const ADDITIONAL_META_TAGS = [ + { + property: 'msapplication-TileColor', + content: '#da532c', + }, + { + name: 'msapplication-config', + content: `${basePath}/icons/browserconfig.xml`, + }, +]; diff --git a/apps/web-stratos/src/screens/home/components/blocks/__snapshots__/index.test.tsx.snap b/apps/web-stratos/src/screens/home/components/blocks/__snapshots__/index.test.tsx.snap new file mode 100644 index 0000000000..28dfe5f09b --- /dev/null +++ b/apps/web-stratos/src/screens/home/components/blocks/__snapshots__/index.test.tsx.snap @@ -0,0 +1,164 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`screen: Home/Blocks/Mobile matches snapshot 1`] = ` +.emotion-0 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; +} + +.emotion-0 .button { + color: #414141; +} + +.emotion-0 .button:hover { + cursor: pointer; +} + +.emotion-1 { + margin-bottom: 16px; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: justify; + -webkit-justify-content: space-between; + justify-content: space-between; +} + +.emotion-2 { + margin: 0; + font-size: 1.5rem; + letter-spacing: 0; + font-family: "Roboto","Helvetica","Arial",sans-serif; + font-weight: 300; + line-height: 1.2; +} + +@media (max-width:1279.95px) { + .emotion-3 { + display: none; + } +} + +@media (min-width:1280px) { + .emotion-4 { + display: none; + } +} + +.emotion-5 { + margin: 0; + -webkit-flex-shrink: 0; + -ms-flex-negative: 0; + flex-shrink: 0; + border-width: 0; + border-style: solid; + border-color: #E8E8E8; + border-bottom-width: thin; +} + +@media (min-width:1280px) { + .emotion-5 { + display: none; + } +} + +.emotion-6 { + padding-top: 16px; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: center; + -ms-flex-pack: center; + -webkit-justify-content: center; + justify-content: center; +} + +@media (min-width:1280px) { + .emotion-6 { + display: none; + } +} + +
+
+

+ latestBlocks +

+ + seeMore + +
+
+ +`; diff --git a/apps/web-stratos/src/screens/home/components/blocks/components/desktop/__snapshots__/index.test.tsx.snap b/apps/web-stratos/src/screens/home/components/blocks/components/desktop/__snapshots__/index.test.tsx.snap new file mode 100644 index 0000000000..d9a717ac1d --- /dev/null +++ b/apps/web-stratos/src/screens/home/components/blocks/components/desktop/__snapshots__/index.test.tsx.snap @@ -0,0 +1,344 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`screen: Home/Blocks/Desktop matches snapshot 1`] = ` +.emotion-0 { + overflow: auto; +} + +.emotion-0 a { + color: #4092CD; +} + +.emotion-1 { + display: table; + width: 100%; + border-collapse: collapse; + border-spacing: 0; +} + +.emotion-1 caption { + font-size: 0.875rem; + letter-spacing: 0.25px; + font-family: "Roboto","Helvetica","Arial",sans-serif; + font-weight: 400; + line-height: 1.43; + padding: 16px; + color: #414141; + text-align: left; + caption-side: bottom; +} + +.emotion-1 .MuiTableBody-root .MuiTableCell-root { + white-space: nowrap; + height: auto; +} + +.emotion-2 { + display: table-header-group; +} + +.emotion-2 { + background-color: initial; +} + +.emotion-3 { + color: inherit; + display: table-row; + vertical-align: middle; + outline: 0; +} + +.emotion-3.MuiTableRow-hover:hover { + background-color: rgba(0, 0, 0, 0.04); +} + +.emotion-3.Mui-selected { + background-color: rgba(255, 131, 91, 0.08); +} + +.emotion-3.Mui-selected:hover { + background-color: rgba(255, 131, 91, 0.12); +} + +.emotion-4 { + font-size: 0.875rem; + letter-spacing: 0.25px; + font-family: "Roboto","Helvetica","Arial",sans-serif; + font-weight: 500; + line-height: 1.5rem; + display: table-cell; + vertical-align: inherit; + border-bottom: 1px solid rgba(252, 252, 252, 1); + text-align: left; + padding: 16px; + color: #000000; +} + +.emotion-4 { + border-bottom: none; + padding: 0 16px; + height: 50px; + font-size: 1rem; +} + +.emotion-7 { + font-size: 0.875rem; + letter-spacing: 0.25px; + font-family: "Roboto","Helvetica","Arial",sans-serif; + font-weight: 500; + line-height: 1.5rem; + display: table-cell; + vertical-align: inherit; + border-bottom: 1px solid rgba(252, 252, 252, 1); + text-align: right; + padding: 16px; + color: #000000; + -webkit-flex-direction: row-reverse; + -ms-flex-direction: row-reverse; + flex-direction: row-reverse; +} + +.emotion-7 { + border-bottom: none; + padding: 0 16px; + height: 50px; + font-size: 1rem; +} + +.emotion-9 { + display: table-row-group; +} + +.emotion-9 .MuiTableRow-root:nth-of-type(odd) { + background-color: #F8F8F8; +} + +.emotion-9 .MuiTableCell-root { + color: #414141; +} + +.emotion-11 { + font-size: 0.875rem; + letter-spacing: 0.25px; + font-family: "Roboto","Helvetica","Arial",sans-serif; + font-weight: 400; + line-height: 1.43; + display: table-cell; + vertical-align: inherit; + border-bottom: 1px solid rgba(252, 252, 252, 1); + text-align: left; + padding: 16px; + color: #000000; +} + +.emotion-11 { + border-bottom: none; + padding: 0 16px; + height: 50px; + font-size: 1rem; +} + +.emotion-14 { + font-size: 0.875rem; + letter-spacing: 0.25px; + font-family: "Roboto","Helvetica","Arial",sans-serif; + font-weight: 400; + line-height: 1.43; + display: table-cell; + vertical-align: inherit; + border-bottom: 1px solid rgba(252, 252, 252, 1); + text-align: right; + padding: 16px; + color: #000000; + -webkit-flex-direction: row-reverse; + -ms-flex-direction: row-reverse; + flex-direction: row-reverse; +} + +.emotion-14 { + border-bottom: none; + padding: 0 16px; + height: 50px; + font-size: 1rem; +} + +
+ + + + + + + + + + + + + + + + + + + +
+ height + + proposer + + hash + + txs + + time +
+ + +
+
+
+
+
+ 76nwV8...hQ857 +
+
+
+ 12 +
+
+
+ + 1 day ago + +
+
+
+`; diff --git a/apps/web-stratos/src/screens/home/components/blocks/components/desktop/index.test.tsx b/apps/web-stratos/src/screens/home/components/blocks/components/desktop/index.test.tsx new file mode 100644 index 0000000000..9a0620df81 --- /dev/null +++ b/apps/web-stratos/src/screens/home/components/blocks/components/desktop/index.test.tsx @@ -0,0 +1,38 @@ +import renderer from 'react-test-renderer'; +import Desktop from '@/screens/home/components/blocks/components/desktop'; +import MockTheme from '@/tests/mocks/MockTheme'; + +// ================================== +// mocks +// ================================== +jest.mock('@/components/avatar_name', () => (props: JSX.IntrinsicElements['div']) => ( +
+)); +// ================================== +// unit tests +// ================================== +describe('screen: Home/Blocks/Desktop', () => { + it('matches snapshot', () => { + const component = renderer.create( + + + + ); + const tree = component?.toJSON(); + expect(tree).toMatchSnapshot(); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); +}); diff --git a/apps/web-stratos/src/screens/home/components/blocks/components/desktop/index.tsx b/apps/web-stratos/src/screens/home/components/blocks/components/desktop/index.tsx new file mode 100644 index 0000000000..34a127dfa0 --- /dev/null +++ b/apps/web-stratos/src/screens/home/components/blocks/components/desktop/index.tsx @@ -0,0 +1,124 @@ +import AvatarName from '@/components/avatar_name'; +import Timestamp from '@/components/Timestamp'; +import { useProfileRecoil } from '@/recoil/profiles/hooks'; +import useStyles from '@/screens/home/components/blocks/components/desktop/styles'; +import { columns } from '@/screens/home/components/blocks/components/desktop/utils'; +import type { ItemType } from '@/screens/home/components/blocks/types'; +import { getMiddleEllipsis } from '@/utils/get_middle_ellipsis'; +import { BLOCK_DETAILS } from '@/utils/go_to_page'; +import Table from '@mui/material/Table'; +import TableBody from '@mui/material/TableBody'; +import TableCell from '@mui/material/TableCell'; +import TableHead from '@mui/material/TableHead'; +import TableRow from '@mui/material/TableRow'; +import { AnimatePresence, motion, Variants } from 'framer-motion'; +import useAppTranslation from '@/hooks/useAppTranslation'; +import Link from 'next/link'; +import numeral from 'numeral'; +import { FC } from 'react'; + +type BlockRowProps = { + item: ItemType; +}; + +const variants: Variants = { + initial: { + height: 0, + display: 'flex', + alignItems: 'center', + overflow: 'hidden', + clipPath: 'inset(0 50 0 50)', + }, + animate: { + height: 50, + display: 'flex', + alignItems: 'center', + overflow: 'hidden', + clipPath: 'inset(0 0 0 0)', + }, + exit: { + height: 50, + display: 'flex', + alignItems: 'center', + overflow: 'hidden', + position: 'absolute', + marginTop: [50, 60], + opacity: 0, + transition: { duration: 0.5 }, + }, +}; + +const BlockRow: FC = ({ item }) => { + const { name, address, imageUrl } = useProfileRecoil(item.proposer); + + const formattedData = { + height: ( + + {numeral(item.height).format('0,0')} + + ), + txs: numeral(item.txs).format('0,0'), + time: , + proposer: , + hash: getMiddleEllipsis(item.hash, { + beginning: 6, + ending: 5, + }), + }; + return ( + + {columns.map((column) => { + const { key, align } = column; + return ( + + + {formattedData[key as keyof typeof formattedData]} + + + ); + })} + + ); +}; + +type DesktopProps = { + className?: string; + items: ItemType[]; +}; + +const Desktop: FC = ({ className, items }) => { + const { t } = useAppTranslation('blocks'); + const { classes, cx } = useStyles(); + + return ( +
+ + + + {columns.map((column) => ( + + {t(column.key)} + + ))} + + + + + {items.map((row) => ( + + ))} + + +
+
+ ); +}; + +export default Desktop; diff --git a/apps/web-stratos/src/screens/home/components/blocks/components/desktop/styles.ts b/apps/web-stratos/src/screens/home/components/blocks/components/desktop/styles.ts new file mode 100644 index 0000000000..c2ead24475 --- /dev/null +++ b/apps/web-stratos/src/screens/home/components/blocks/components/desktop/styles.ts @@ -0,0 +1,20 @@ +import { makeStyles } from 'tss-react/mui'; + +const useStyles = makeStyles()((theme) => ({ + root: { + overflow: 'auto', + '& a': { + color: theme.palette.custom.fonts.highlight, + }, + }, + table: { + '& .MuiTableBody-root': { + '& .MuiTableCell-root': { + whiteSpace: 'nowrap', + height: 'auto', + }, + }, + }, +})); + +export default useStyles; diff --git a/apps/web-stratos/src/screens/home/components/blocks/components/desktop/utils.tsx b/apps/web-stratos/src/screens/home/components/blocks/components/desktop/utils.tsx new file mode 100644 index 0000000000..b3b5ca5763 --- /dev/null +++ b/apps/web-stratos/src/screens/home/components/blocks/components/desktop/utils.tsx @@ -0,0 +1,22 @@ +export const columns: { + key: string; + align?: 'left' | 'center' | 'right' | 'justify' | 'inherit'; +}[] = [ + { + key: 'height', + }, + { + key: 'proposer', + }, + { + key: 'hash', + }, + { + key: 'txs', + align: 'right', + }, + { + key: 'time', + align: 'right', + }, +]; diff --git a/apps/web-stratos/src/screens/home/components/blocks/components/index.ts b/apps/web-stratos/src/screens/home/components/blocks/components/index.ts new file mode 100644 index 0000000000..1a39d26a2b --- /dev/null +++ b/apps/web-stratos/src/screens/home/components/blocks/components/index.ts @@ -0,0 +1,4 @@ +import Desktop from '@/screens/home/components/blocks/components/desktop'; +import Mobile from '@/screens/home/components/blocks/components/mobile'; + +export { Mobile, Desktop }; diff --git a/apps/web-stratos/src/screens/home/components/blocks/components/mobile/__snapshots__/index.test.tsx.snap b/apps/web-stratos/src/screens/home/components/blocks/components/mobile/__snapshots__/index.test.tsx.snap new file mode 100644 index 0000000000..3d14c4bf9d --- /dev/null +++ b/apps/web-stratos/src/screens/home/components/blocks/components/mobile/__snapshots__/index.test.tsx.snap @@ -0,0 +1,33 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`screen: Home/Blocks/Mobile matches snapshot 1`] = ` +
+
+ 4,000 + + } + id="SingleBlockMobile" + proposer={ + + } + time={ + + } + txs="12" + /> +
+`; diff --git a/apps/web-stratos/src/screens/home/components/blocks/components/mobile/index.test.tsx b/apps/web-stratos/src/screens/home/components/blocks/components/mobile/index.test.tsx new file mode 100644 index 0000000000..9f47661d3c --- /dev/null +++ b/apps/web-stratos/src/screens/home/components/blocks/components/mobile/index.test.tsx @@ -0,0 +1,42 @@ +import renderer from 'react-test-renderer'; +import Mobile from '@/screens/home/components/blocks/components/mobile'; +import MockTheme from '@/tests/mocks/MockTheme'; + +// ================================== +// mocks +// ================================== +jest.mock('@/components/single_block_mobile', () => (props: JSX.IntrinsicElements['div']) => ( +
+)); +jest.mock('@/components/avatar_name', () => (props: JSX.IntrinsicElements['div']) => ( +
+)); + +// ================================== +// unit tests +// ================================== +describe('screen: Home/Blocks/Mobile', () => { + it('matches snapshot', () => { + const component = renderer.create( + + + + ); + const tree = component?.toJSON(); + expect(tree).toMatchSnapshot(); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); +}); diff --git a/apps/web-stratos/src/screens/home/components/blocks/components/mobile/index.tsx b/apps/web-stratos/src/screens/home/components/blocks/components/mobile/index.tsx new file mode 100644 index 0000000000..e71fb0e440 --- /dev/null +++ b/apps/web-stratos/src/screens/home/components/blocks/components/mobile/index.tsx @@ -0,0 +1,55 @@ +import AvatarName from '@/components/avatar_name'; +import SingleBlockMobile from '@/components/single_block_mobile'; +import Timestamp from '@/components/Timestamp'; +import { useProfileRecoil } from '@/recoil/profiles/hooks'; +import type { ItemType } from '@/screens/home/components/blocks/types'; +import { getMiddleEllipsis } from '@/utils/get_middle_ellipsis'; +import { BLOCK_DETAILS } from '@/utils/go_to_page'; +import Divider from '@mui/material/Divider'; +import Link from 'next/link'; +import numeral from 'numeral'; +import { FC, Fragment } from 'react'; + +type BlocksItemProps = { + item: ItemType; + i: number; + isLast: boolean; +}; + +const BlocksItem: FC = ({ item, i, isLast }) => { + const { name, address, imageUrl } = useProfileRecoil(item.proposer); + return ( + + + {numeral(item.height).format('0,0')} + + } + txs={numeral(item.txs).format('0,0')} + time={} + proposer={} + hash={getMiddleEllipsis(item.hash, { + beginning: 13, + ending: 10, + })} + /> + {!isLast && } + + ); +}; + +type MobileProps = { + className?: string; + items: ItemType[]; +}; + +const Mobile: FC = ({ className, items }) => ( +
+ {items?.map((x, i) => ( + + ))} +
+); + +export default Mobile; diff --git a/apps/web-stratos/src/screens/home/components/blocks/hooks.ts b/apps/web-stratos/src/screens/home/components/blocks/hooks.ts new file mode 100644 index 0000000000..5775798f8a --- /dev/null +++ b/apps/web-stratos/src/screens/home/components/blocks/hooks.ts @@ -0,0 +1,50 @@ +import * as R from 'ramda'; +import { useCallback, useState } from 'react'; +import { + BlocksListenerSubscription, + useBlocksListenerSubscription, +} from '@/graphql/types/general_types'; +import type { BlocksState } from '@/screens/home/components/blocks/types'; + +const formatBlocks = (data: BlocksListenerSubscription) => + data.blocks.map((x) => { + const proposerAddress = x?.validator?.validatorInfo?.operatorAddress ?? ''; + return { + height: x.height, + txs: x.txs ?? 0, + hash: x.hash, + timestamp: x.timestamp, + proposer: proposerAddress, + }; + }) ?? []; + +export const useBlocks = () => { + const [state, setState] = useState({ + loading: true, + items: [], + }); + + const handleSetState = useCallback((stateChange: (prevState: BlocksState) => BlocksState) => { + setState((prevState) => { + const newState = stateChange(prevState); + return R.equals(prevState, newState) ? prevState : newState; + }); + }, []); + + // ================================ + // block subscription + // ================================ + useBlocksListenerSubscription({ + onData: (data) => { + handleSetState((prevState) => ({ + ...prevState, + loading: false, + items: data.data.data ? formatBlocks(data.data.data) : [], + })); + }, + }); + + return { + state, + }; +}; diff --git a/apps/web-stratos/src/screens/home/components/blocks/index.test.tsx b/apps/web-stratos/src/screens/home/components/blocks/index.test.tsx new file mode 100644 index 0000000000..a6213aa970 --- /dev/null +++ b/apps/web-stratos/src/screens/home/components/blocks/index.test.tsx @@ -0,0 +1,81 @@ +import { BlocksListenerDocument } from '@/graphql/types/general_types'; +import Blocks from '@/screens/home/components/blocks'; +import { mockClient } from '@/tests/mocks/mockApollo'; +import MockTheme from '@/tests/mocks/MockTheme'; +import wait from '@/tests/utils/wait'; +import { ApolloProvider } from '@apollo/client'; +import { MockedProvider } from '@apollo/client/testing'; +import renderer from 'react-test-renderer'; + +// ================================== +// mocks +// ================================== +jest.mock('@/components/box', () => (props: JSX.IntrinsicElements['div']) => ( +
+)); +jest.mock('@/components/no_data', () => (props: JSX.IntrinsicElements['div']) => ( +
+)); + +jest.mock( + '@/screens/home/components/blocks/components/mobile', + () => (props: JSX.IntrinsicElements['div']) =>
+); +jest.mock( + '@/screens/home/components/blocks/components/desktop', + () => (props: JSX.IntrinsicElements['div']) =>
+); + +const mockBlocksListenerDocument = jest.fn().mockReturnValue({ + data: { + blocks: [ + { + height: 379643, + txs: 2, + hash: 'D0243447726B8BD7AE94BF4F98E536A647959194E870AB8566CB833A3CC847F6', + timestamp: '2021-05-24T05:28:05.839448', + validator: { + validatorInfo: { + operatorAddress: 'desmosvaloper1h5f3dywec65v9qulxkmcv3e6yujyh3zm0ghhl3', + }, + }, + }, + ], + }, +}); + +// ================================== +// unit tests +// ================================== +describe('screen: Home/Blocks/Mobile', () => { + it('matches snapshot', async () => { + let component: renderer.ReactTestRenderer | undefined; + + renderer.act(() => { + component = renderer.create( + + + + + + + + ); + }); + + await wait(renderer.act); + const tree = component?.toJSON(); + expect(tree).toMatchSnapshot(); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); +}); diff --git a/apps/web-stratos/src/screens/home/components/blocks/index.tsx b/apps/web-stratos/src/screens/home/components/blocks/index.tsx new file mode 100644 index 0000000000..c787ffbca3 --- /dev/null +++ b/apps/web-stratos/src/screens/home/components/blocks/index.tsx @@ -0,0 +1,54 @@ +/* eslint-disable no-nested-ternary */ +import Box from '@/components/box'; +import Loading from '@/components/loading'; +import NoData from '@/components/no_data'; +import Desktop from '@/screens/home/components/blocks/components/desktop'; +import Mobile from '@/screens/home/components/blocks/components/mobile'; +import { useBlocks } from '@/screens/home/components/blocks/hooks'; +import useStyles from '@/screens/home/components/blocks/styles'; +import { useDisplayStyles } from '@/styles/useSharedStyles'; +import { BLOCKS } from '@/utils/go_to_page'; +import Divider from '@mui/material/Divider'; +import Typography from '@mui/material/Typography'; +import useAppTranslation from '@/hooks/useAppTranslation'; +import Link from 'next/link'; +import { FC } from 'react'; + +const Blocks: FC = ({ className }) => { + const { t } = useAppTranslation('home'); + const { classes, cx } = useStyles(); + const display = useDisplayStyles().classes; + const { state } = useBlocks(); + + return ( + +
+ {t('latestBlocks')} + + {t('seeMore')} + +
+ {state.items.length ? ( + <> + + + + + {t('seeMore')} + + + ) : state.loading ? ( + + ) : ( + + )} +
+ ); +}; + +export default Blocks; diff --git a/apps/web-stratos/src/screens/home/components/blocks/styles.ts b/apps/web-stratos/src/screens/home/components/blocks/styles.ts new file mode 100644 index 0000000000..972171c8ba --- /dev/null +++ b/apps/web-stratos/src/screens/home/components/blocks/styles.ts @@ -0,0 +1,28 @@ +import { makeStyles } from 'tss-react/mui'; + +const useStyles = makeStyles()((theme) => ({ + root: { + display: 'flex', + flexDirection: 'column', + '& .button': { + color: theme.palette.custom.fonts.fontTwo, + '&:hover': { + cursor: 'pointer', + }, + }, + }, + label: { + marginBottom: theme.spacing(2), + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + }, + seeMoreFooter: { + paddingTop: theme.spacing(2), + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + }, +})); + +export default useStyles; diff --git a/apps/web-stratos/src/screens/home/components/blocks/types.ts b/apps/web-stratos/src/screens/home/components/blocks/types.ts new file mode 100644 index 0000000000..70da7ebe40 --- /dev/null +++ b/apps/web-stratos/src/screens/home/components/blocks/types.ts @@ -0,0 +1,14 @@ +export interface BlockType { + height: number; + txs: number; + timestamp: string; + proposer: string; + hash: string; +} + +export interface BlocksState { + loading: boolean; + items: BlockType[]; +} + +export type ItemType = BlockType; diff --git a/apps/web-stratos/src/screens/home/components/charts/TokenomicChart.tsx b/apps/web-stratos/src/screens/home/components/charts/TokenomicChart.tsx new file mode 100644 index 0000000000..07c89e2a27 --- /dev/null +++ b/apps/web-stratos/src/screens/home/components/charts/TokenomicChart.tsx @@ -0,0 +1,23 @@ +import { useId, FC } from 'react'; + +import { Tooltip, BarChart, CartesianGrid, XAxis, YAxis, Bar } from 'recharts'; + +interface ITokenomicChart { + data?: any[]; +} + +const TokenomicChart: FC = ({ data }) => { + const chartId = useId(); + + return ( + + + + + + + + ); +}; + +export default TokenomicChart; diff --git a/apps/web-stratos/src/screens/home/components/consensus/hooks.ts b/apps/web-stratos/src/screens/home/components/consensus/hooks.ts new file mode 100644 index 0000000000..13d2c2ba9c --- /dev/null +++ b/apps/web-stratos/src/screens/home/components/consensus/hooks.ts @@ -0,0 +1,219 @@ +import numeral from 'numeral'; +import * as R from 'ramda'; +import { useCallback, useEffect, useState } from 'react'; +import chainConfig from '@/chainConfig'; +import useShallowMemo from '@/hooks/useShallowMemo'; +import { hexToBech32 } from '@/utils/hex_to_bech32'; + +const { endpoints, prefix } = chainConfig(); + +/* Checking if the code is running on the server or the client. */ +const ssrMode = typeof window === 'undefined'; + +type NewStepResult = { + query: "tm.event='NewRoundStep'"; + data: { + type: 'tendermint/event/RoundState'; + value: { + height: string; // '7030123'; + round: number; // 0; + step: 'RoundStepPropose'; + }; + }; + events: { + 'tm.event': ['NewRoundStep']; + }; +}; + +type NewRoundResult = { + query: "tm.event='NewRound'"; + data: { + type: 'tendermint/event/NewRound'; + value: { + height: string; // '7030123'; + round: number; // 0; + step: 'RoundStepNewRound'; + proposer: { + address: string; // '2DA0A56810303EC8EFF8787C4F0ABE36C918C1C7'; + index: number; // 30; + }; + }; + }; + events: { + 'tm.event': ['NewRound']; + }; +}; + +const wsEndpoints = [ + process.env.NEXT_PUBLIC_RPC_WEBSOCKET, + endpoints.publicRpcWebsocket, + endpoints.graphqlWebsocket, + 'ws://localhost:3000/websocket', +]; + +const stepReference = { + 0: 0, + RoundStepNewHeight: 1, + RoundStepPropose: 2, + RoundStepPrevote: 3, + RoundStepPrecommit: 4, + RoundStepCommit: 5, +}; + +/* It's the number of milliseconds to wait before sending the subscription again. */ +const KEEP_ALIVE = 30 * 1000; + +let client: WebSocket | null; + +/* It's a JSON-RPC request to subscribe to the NewRound event. */ +const stepHeader = JSON.stringify({ + jsonrpc: '2.0', + method: 'subscribe', + id: 0, + params: { + query: "tm.event='NewRoundStep'", + }, +}); + +/* It's a JSON-RPC request to subscribe to the NewRound event. */ +const roundHeader = JSON.stringify({ + jsonrpc: '2.0', + method: 'subscribe', + id: 0, + params: { + query: "tm.event='NewRound'", + }, +}); + +/* It's a keep alive message. */ +const pingHeader = JSON.stringify({ + type: 'ping', +}); + +/** + * If the client is not ready, wait a second and try again. If the client is ready, send the data and + * if there's an error, wait a second and try again. If there's no error, and there's a keepAlive + * value, wait the keepAlive value and try again + * @param {WebSocket} ws - The WebSocket client that we want to subscribe to the data feed. + * @param {string} data - The data to send to the server. + * @param {number} [keepAlive] - The number of milliseconds to wait before sending the subscription + * again. + */ +function subscribe(ws: WebSocket | null, data: string, keepAlive?: number) { + /* It's checking if the client is still the same client that we created. If it's not, it means + that the client has been replaced and we don't want to send any more data to this client. */ + if (!ws || ws !== client) { + return; + } + + if (ws.readyState !== WebSocket.OPEN) { + setTimeout(() => subscribe(ws, data, keepAlive), 1000); + } else { + try { + ws.send(data); + if (keepAlive) setTimeout(() => subscribe(ws, data, keepAlive), keepAlive); + } catch (err) { + console.error(err); + setTimeout(() => subscribe(ws, data, keepAlive), 1000); + } + } +} + +/** + * It connects to a websocket, subscribes to the events we want, and then calls the appropriate + * callback function when it receives an event + * @param formatNewRound - (data: unknown) => void + * @param formatNewStep - (data: unknown) => void + * @returns A WebSocket client + */ +function useConnect() { + const [loadingNewRound, setLoadingNewRound] = useState(true); + const [newRound, setNewRound] = useState(null); + const [loadingNewStep, setLoadingNewStep] = useState(true); + const [newStep, setNewStep] = useState(null); + + const connect = useCallback(() => { + const ws = new WebSocket(wsEndpoints.find((u) => u) ?? ''); + const reconnectTimer = setTimeout(() => ws.close(), (12 + Math.random() * 4) * 1000); + + ws.onopen = () => { + clearTimeout(reconnectTimer); + subscribe(ws, stepHeader); + subscribe(ws, roundHeader); + subscribe(ws, pingHeader, KEEP_ALIVE); + }; + ws.onmessage = (e) => { + if (ws !== client) return; + if (ws?.readyState !== WebSocket.OPEN) return; + const data = JSON.parse(e.data as string); + const event = R.pathOr('', ['result', 'data', 'type'], data); + if (event === 'tendermint/event/NewRound') { + setNewRound((prevState: unknown | null) => (R.equals(prevState, data) ? prevState : data)); + setLoadingNewRound(false); + } else if (event === 'tendermint/event/RoundState') { + setNewStep((prevState: unknown | null) => (R.equals(prevState, data) ? prevState : data)); + setLoadingNewStep(false); + } + }; + ws.onclose = () => { + console.warn('closing socket'); + setTimeout(() => { + setLoadingNewRound(true); + setLoadingNewStep(true); + connect(); + }, 1000); + }; + ws.onerror = (err: unknown) => { + console.error('Socket encountered error', err); + }; + client = ws; + }, []); + useEffect(() => { + if (!ssrMode) connect(); + return () => { + client?.close(); + client = null; + }; + }, [connect]); + + return { loadingNewRound, loadingNewStep, newRound, newStep }; +} + +const TOTAL_STEPS = 5; + +/* A callback function that is called when the websocket receives a new round event. */ +const formatNewRound = (data: unknown) => { + const result = R.pathOr(null, ['result'], data); + const height = numeral(result?.data.value.height).value() ?? 0; + const proposerHex = result?.data.value.proposer.address ?? ''; + const consensusAddress = hexToBech32(proposerHex, prefix.consensus); + return { height, proposer: consensusAddress }; +}; + +/* A callback function that is called when the websocket receives a new round event. */ +const formatNewStep = (data: unknown) => { + const result = R.pathOr(null, ['result'], data); + const round = result?.data.value.round ?? 0; + const step = stepReference[result?.data.value.step ?? 0]; + const roundCompletion = (step / TOTAL_STEPS) * 100; + return { round, step, roundCompletion }; +}; + +/** + * It creates a new websocket connection and closes it when the component unmounts + * @returns The state of the consensus. + */ +export const useConsensus = () => { + const { loadingNewRound, loadingNewStep, newRound, newStep } = useConnect(); + const stateMemo = useShallowMemo({ + loadingNewRound, + loadingNewStep, + ...formatNewRound(newRound), + ...formatNewStep(newStep), + totalSteps: TOTAL_STEPS, + }); + + return { + state: stateMemo, + }; +}; diff --git a/apps/web-stratos/src/screens/home/components/consensus/index.tsx b/apps/web-stratos/src/screens/home/components/consensus/index.tsx new file mode 100644 index 0000000000..f5ee847040 --- /dev/null +++ b/apps/web-stratos/src/screens/home/components/consensus/index.tsx @@ -0,0 +1,107 @@ +import AvatarName from '@/components/avatar_name'; +import Box from '@/components/box'; +import Loading from '@/components/loading'; +import { useProfileRecoil } from '@/recoil/profiles/hooks'; +import { useConsensus } from '@/screens/home/components/consensus/hooks'; +import useStyles from '@/screens/home/components/consensus/styles'; +import Typography from '@mui/material/Typography'; +import useAppTranslation from '@/hooks/useAppTranslation'; +import dynamic from 'next/dynamic'; +import numeral from 'numeral'; +import { FC } from 'react'; +import { PolarAngleAxis, RadialBar, RadialBarChart, Tooltip } from 'recharts'; + +const DynamicRadialBarChart = dynamic(() => Promise.resolve(RadialBarChart), { ssr: false }); + +const Consensus: FC = ({ className }) => { + const { classes, cx, theme } = useStyles(); + const { state } = useConsensus(); + const { t } = useAppTranslation('home'); + + const data = [ + { + value: state.roundCompletion, + fill: theme.palette.primary.main, + }, + ]; + + const circleSize = 200; + const { name, address, imageUrl } = useProfileRecoil(state.proposer); + + return ( + + + {t('consensus')} + +
+
+ + {t('height')} + + + {t('proposer')} + +
+
+ + {state.loadingNewRound ? '-' : numeral(state.height).format('0,0')} + + {!state.loadingNewStep && state.proposer ? ( + + ) : ( + '-' + )} +
+
+
+ {state.loadingNewStep ? ( + + ) : ( + + + + + + + {t('step', { + step: numeral(state.step).format('0,0'), + })} + + + + + {t('round', { + round: numeral(state.round).format('0,0'), + })} + + + + )} +
+
+ ); +}; + +export default Consensus; diff --git a/apps/web-stratos/src/screens/home/components/consensus/styles.ts b/apps/web-stratos/src/screens/home/components/consensus/styles.ts new file mode 100644 index 0000000000..088bbdd3b2 --- /dev/null +++ b/apps/web-stratos/src/screens/home/components/consensus/styles.ts @@ -0,0 +1,60 @@ +import { makeStyles } from 'tss-react/mui'; +import Color from 'color'; + +const useStyles = makeStyles()((theme) => ({ + root: { + height: '100%', + display: 'flex', + justifyContent: 'center', + flexDirection: 'column', + overflow: 'auto', + }, + content: { + flex: 1, + display: 'flex', + alignItems: 'center', + justifyContent: 'space-around', + flexDirection: 'column', + }, + label: { + marginBottom: theme.spacing(2), + }, + chart: { + '& .recharts-radial-bar-background-sector': { + fill: Color(theme.palette.primary.main).alpha(0.4).toString(), + }, + }, + chartPercentLabel: { + fontSize: '2rem', + fill: theme.palette.custom.fonts.fontOne, + }, + chartExtraLabel: { + fill: theme.palette.custom.fonts.fontTwo, + }, + chartLabel: { + fontSize: '1rem', + color: theme.palette.custom.fonts.fontOne, + }, + info: { + display: 'flex', + flexDirection: 'column', + width: '100%', + color: theme.palette.custom.fonts.fontTwo, + '& > *': { + display: 'flex', + alignItems: 'center', + '& > *': { + width: '50%', + }, + }, + '& .label': { + color: theme.palette.custom.fonts.fontThree, + marginBottom: theme.spacing(0.5), + }, + [theme.breakpoints.up('lg')]: { + marginBottom: 0, + }, + }, +})); + +export default useStyles; diff --git a/apps/web-stratos/src/screens/home/components/data_blocks/__snapshots__/index.test.tsx.snap b/apps/web-stratos/src/screens/home/components/data_blocks/__snapshots__/index.test.tsx.snap new file mode 100644 index 0000000000..7c9e35c195 --- /dev/null +++ b/apps/web-stratos/src/screens/home/components/data_blocks/__snapshots__/index.test.tsx.snap @@ -0,0 +1,403 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`screen: Home/DataBlocks matches snapshot 1`] = ` +.emotion-0 { + display: grid; + gap: 8px; + grid-template-rows: auto; +} + +@media (min-width:375px) { + .emotion-0 { + grid-template-columns: repeat(2, 1fr); + } +} + +@media (min-width:1280px) { + .emotion-0 { + gap: 16px; + grid-template-columns: repeat(4, 1fr); + } +} + +.emotion-1 { + padding: 16px; + background: #FF835B; + border-radius: 4px; + height: 110px; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + -webkit-align-items: flex-start; + -webkit-box-align: flex-start; + -ms-flex-align: flex-start; + align-items: flex-start; + -webkit-box-pack: justify; + -webkit-justify-content: space-between; + justify-content: space-between; + color: #FFFFFF; + background: #F87255; +} + +.emotion-1 .label { + margin-bottom: 16px; +} + +.emotion-1 .content { + width: 100%; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-align-items: flex-end; + -webkit-box-align: flex-end; + -ms-flex-align: flex-end; + align-items: flex-end; + -webkit-box-pack: justify; + -webkit-justify-content: space-between; + justify-content: space-between; +} + +.emotion-1 .description { + display: none; +} + +@media (min-width:768px) { + .emotion-1 .description { + display: block; + } +} + +.emotion-1 .flexContent { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: row; + -ms-flex-direction: row; + flex-direction: row; + gap: 4px; +} + +.emotion-2 { + margin: 0; + font-size: 0.875rem; + letter-spacing: 0.25px; + font-family: "Roboto","Helvetica","Arial",sans-serif; + font-weight: 400; + line-height: 1.43; +} + +.emotion-3 { + margin: 0; + font-size: 2rem; + letter-spacing: 0.25px; + font-family: "Roboto","Helvetica","Arial",sans-serif; + font-weight: 300; + line-height: 1.167; +} + +.emotion-4 { + padding: 16px; + background: #FF835B; + border-radius: 4px; + height: 110px; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + -webkit-align-items: flex-start; + -webkit-box-align: flex-start; + -ms-flex-align: flex-start; + align-items: flex-start; + -webkit-box-pack: justify; + -webkit-justify-content: space-between; + justify-content: space-between; + color: #FFFFFF; + background: #FA9147; +} + +.emotion-4 .label { + margin-bottom: 16px; +} + +.emotion-4 .content { + width: 100%; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-align-items: flex-end; + -webkit-box-align: flex-end; + -ms-flex-align: flex-end; + align-items: flex-end; + -webkit-box-pack: justify; + -webkit-justify-content: space-between; + justify-content: space-between; +} + +.emotion-4 .description { + display: none; +} + +@media (min-width:768px) { + .emotion-4 .description { + display: block; + } +} + +.emotion-4 .flexContent { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: row; + -ms-flex-direction: row; + flex-direction: row; + gap: 4px; +} + +.emotion-7 { + padding: 16px; + background: #FF835B; + border-radius: 4px; + height: 110px; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + -webkit-align-items: flex-start; + -webkit-box-align: flex-start; + -ms-flex-align: flex-start; + align-items: flex-start; + -webkit-box-pack: justify; + -webkit-justify-content: space-between; + justify-content: space-between; + color: #FFFFFF; + background: #20D494; +} + +.emotion-7 .label { + margin-bottom: 16px; +} + +.emotion-7 .content { + width: 100%; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-align-items: flex-end; + -webkit-box-align: flex-end; + -ms-flex-align: flex-end; + align-items: flex-end; + -webkit-box-pack: justify; + -webkit-justify-content: space-between; + justify-content: space-between; +} + +.emotion-7 .description { + display: none; +} + +@media (min-width:768px) { + .emotion-7 .description { + display: block; + } +} + +.emotion-7 .flexContent { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: row; + -ms-flex-direction: row; + flex-direction: row; + gap: 4px; +} + +.emotion-10 { + margin: 0; + font-size: 0.75rem; + letter-spacing: 0.4px; + font-family: "Roboto","Helvetica","Arial",sans-serif; + font-weight: 400; + line-height: 1.66; +} + +.emotion-11 { + padding: 16px; + background: #FF835B; + border-radius: 4px; + height: 110px; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + -webkit-align-items: flex-start; + -webkit-box-align: flex-start; + -ms-flex-align: flex-start; + align-items: flex-start; + -webkit-box-pack: justify; + -webkit-justify-content: space-between; + justify-content: space-between; + color: #FFFFFF; + background: #2FB6E0; +} + +.emotion-11 .label { + margin-bottom: 16px; +} + +.emotion-11 .content { + width: 100%; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-align-items: flex-end; + -webkit-box-align: flex-end; + -ms-flex-align: flex-end; + align-items: flex-end; + -webkit-box-pack: justify; + -webkit-justify-content: space-between; + justify-content: space-between; +} + +.emotion-11 .description { + display: none; +} + +@media (min-width:768px) { + .emotion-11 .description { + display: block; + } +} + +.emotion-11 .flexContent { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: row; + -ms-flex-direction: row; + flex-direction: row; + gap: 4px; +} + +
+
+

+ latestBlock +

+
+

+ 953,992 +

+
+
+
+
+

+ averageBlockTime +

+
+

+ 6.54 s +

+
+
+
+
+

+ price +

+
+

+ N/A +

+
+ + dataFrom + + +
+
+
+
+

+ activeValidators +

+
+

+ 200 +

+
+ + outOfValidators + +
+
+
+
+`; diff --git a/apps/web-stratos/src/screens/home/components/data_blocks/components/index.ts b/apps/web-stratos/src/screens/home/components/data_blocks/components/index.ts new file mode 100644 index 0000000000..c565679804 --- /dev/null +++ b/apps/web-stratos/src/screens/home/components/data_blocks/components/index.ts @@ -0,0 +1,3 @@ +import SingleBlock from '@/screens/home/components/data_blocks/components/single_block'; + +export { SingleBlock }; diff --git a/apps/web-stratos/src/screens/home/components/data_blocks/components/single_block/__snapshots__/index.test.tsx.snap b/apps/web-stratos/src/screens/home/components/data_blocks/components/single_block/__snapshots__/index.test.tsx.snap new file mode 100644 index 0000000000..e4fd676186 --- /dev/null +++ b/apps/web-stratos/src/screens/home/components/data_blocks/components/single_block/__snapshots__/index.test.tsx.snap @@ -0,0 +1,105 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`screen: Home/SingleBlock matches snapshot 1`] = ` +.emotion-0 { + padding: 16px; + background: #FF835B; + border-radius: 4px; + height: 110px; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + -webkit-align-items: flex-start; + -webkit-box-align: flex-start; + -ms-flex-align: flex-start; + align-items: flex-start; + -webkit-box-pack: justify; + -webkit-justify-content: space-between; + justify-content: space-between; + color: #FFFFFF; +} + +.emotion-0 .label { + margin-bottom: 16px; +} + +.emotion-0 .content { + width: 100%; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-align-items: flex-end; + -webkit-box-align: flex-end; + -ms-flex-align: flex-end; + align-items: flex-end; + -webkit-box-pack: justify; + -webkit-justify-content: space-between; + justify-content: space-between; +} + +.emotion-0 .description { + display: none; +} + +@media (min-width:768px) { + .emotion-0 .description { + display: block; + } +} + +.emotion-0 .flexContent { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: row; + -ms-flex-direction: row; + flex-direction: row; + gap: 4px; +} + +.emotion-1 { + margin: 0; + font-size: 0.875rem; + letter-spacing: 0.25px; + font-family: "Roboto","Helvetica","Arial",sans-serif; + font-weight: 400; + line-height: 1.43; +} + +.emotion-2 { + margin: 0; + font-size: 2rem; + letter-spacing: 0.25px; + font-family: "Roboto","Helvetica","Arial",sans-serif; + font-weight: 300; + line-height: 1.167; +} + +
+

+ Price +

+
+

+ $4.40 +

+
+
+
+`; diff --git a/apps/web-stratos/src/screens/home/components/data_blocks/components/single_block/index.test.tsx b/apps/web-stratos/src/screens/home/components/data_blocks/components/single_block/index.test.tsx new file mode 100644 index 0000000000..40b82234af --- /dev/null +++ b/apps/web-stratos/src/screens/home/components/data_blocks/components/single_block/index.test.tsx @@ -0,0 +1,22 @@ +import renderer from 'react-test-renderer'; +import SingleBlock from '@/screens/home/components/data_blocks/components/single_block'; +import MockTheme from '@/tests/mocks/MockTheme'; + +// ================================== +// unit tests +// ================================== +describe('screen: Home/SingleBlock', () => { + it('matches snapshot', () => { + const component = renderer.create( + + + + ); + const tree = component?.toJSON(); + expect(tree).toMatchSnapshot(); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); +}); diff --git a/apps/web-stratos/src/screens/home/components/data_blocks/components/single_block/index.tsx b/apps/web-stratos/src/screens/home/components/data_blocks/components/single_block/index.tsx new file mode 100644 index 0000000000..8be26a9c2d --- /dev/null +++ b/apps/web-stratos/src/screens/home/components/data_blocks/components/single_block/index.tsx @@ -0,0 +1,36 @@ +import Typography from '@mui/material/Typography'; +import { FC } from 'react'; +import useStyles from '@/screens/home/components/data_blocks/components/single_block/styles'; + +type SingleBlockProps = { + className?: string; + label: string; + value: string; + description?: string; + Icon?: JSX.Element; +}; + +const SingleBlock: FC = ({ className, label, value, description, Icon }) => { + const { classes, cx } = useStyles(); + + return ( +
+ + {label} + +
+ {value} +
+ {!!description && ( + + {description} + + )} + {!!Icon && Icon} +
+
+
+ ); +}; + +export default SingleBlock; diff --git a/apps/web-stratos/src/screens/home/components/data_blocks/components/single_block/styles.ts b/apps/web-stratos/src/screens/home/components/data_blocks/components/single_block/styles.ts new file mode 100644 index 0000000000..4bad9d28b3 --- /dev/null +++ b/apps/web-stratos/src/screens/home/components/data_blocks/components/single_block/styles.ts @@ -0,0 +1,37 @@ +import { makeStyles } from 'tss-react/mui'; + +const useStyles = makeStyles()((theme) => ({ + root: { + padding: theme.spacing(2), + background: theme.palette.primary.main, + borderRadius: theme.shape.borderRadius, + height: '110px', + display: 'flex', + flexDirection: 'column', + alignItems: 'flex-start', + justifyContent: 'space-between', + color: theme.palette.custom.fonts.fontFive, + '& .label': { + marginBottom: theme.spacing(2), + }, + '& .content': { + width: '100%', + display: 'flex', + alignItems: 'flex-end', + justifyContent: 'space-between', + }, + '& .description': { + display: 'none', + [theme.breakpoints.up('md')]: { + display: 'block', + }, + }, + '& .flexContent': { + display: 'flex', + flexDirection: 'row', + gap: theme.spacing(0.5), + }, + }, +})); + +export default useStyles; diff --git a/apps/web-stratos/src/screens/home/components/data_blocks/hooks.ts b/apps/web-stratos/src/screens/home/components/data_blocks/hooks.ts new file mode 100644 index 0000000000..adef74cfad --- /dev/null +++ b/apps/web-stratos/src/screens/home/components/data_blocks/hooks.ts @@ -0,0 +1,107 @@ +import numeral from 'numeral'; +import { useState } from 'react'; +import chainConfig from '@/chainConfig'; +import { + ActiveValidatorCountQuery, + AverageBlockTimeQuery, + TokenPriceListenerSubscription, + useActiveValidatorCountQuery, + useAverageBlockTimeQuery, + useLatestBlockHeightListenerSubscription, + useTokenPriceListenerSubscription, +} from '@/graphql/types/general_types'; + +const { primaryTokenUnit, tokenUnits } = chainConfig(); + +type DataBlocksState = { + blockHeight: number; + blockTime: number; + price: number | null; + validators: { + active: number; + total: number; + }; +}; + +const formatAverageBlockTime = (data: AverageBlockTimeQuery, state: DataBlocksState) => + data.averageBlockTime[0]?.averageTime ?? state.blockTime; + +const formatTokenPrice = (data: TokenPriceListenerSubscription, state: DataBlocksState) => { + if (data?.tokenPrice[0]?.price) { + return numeral(numeral(data?.tokenPrice[0]?.price).format('0.0000', Math.floor)).value(); + } + return state.price; +}; + +const formatActiveValidatorsCount = (data: ActiveValidatorCountQuery) => ({ + active: data.activeTotal.aggregate?.count ?? 0, + total: data.total?.aggregate?.count ?? 0, +}); + +export const useDataBlocks = () => { + const [state, setState] = useState({ + blockHeight: 0, + blockTime: 0, + price: null, + validators: { + active: 0, + total: 0, + }, + }); + + // ==================================== + // block height + // ==================================== + + useLatestBlockHeightListenerSubscription({ + onData: (data) => { + setState((prevState) => ({ + ...prevState, + blockHeight: data.data.data?.height?.[0]?.height ?? 0, + })); + }, + }); + + // ==================================== + // block time + // ==================================== + useAverageBlockTimeQuery({ + onCompleted: (data) => { + setState((prevState) => ({ + ...prevState, + blockTime: formatAverageBlockTime(data, state), + })); + }, + }); + + // ==================================== + // token price + // ==================================== + useTokenPriceListenerSubscription({ + variables: { + denom: tokenUnits?.[primaryTokenUnit]?.display, + }, + onData: (data) => { + setState((prevState) => ({ + ...prevState, + price: data.data.data ? formatTokenPrice(data.data.data, state) : 0, + })); + }, + }); + + // ==================================== + // validators + // ==================================== + useActiveValidatorCountQuery({ + onCompleted: (data) => { + setState((prevState) => ({ + ...prevState, + validators: formatActiveValidatorsCount(data), + })); + }, + }); + + return { + state, + }; +}; diff --git a/apps/web-stratos/src/screens/home/components/data_blocks/index.test.tsx b/apps/web-stratos/src/screens/home/components/data_blocks/index.test.tsx new file mode 100644 index 0000000000..71d9d22bac --- /dev/null +++ b/apps/web-stratos/src/screens/home/components/data_blocks/index.test.tsx @@ -0,0 +1,110 @@ +import { ApolloClient, ApolloProvider, from, InMemoryCache } from '@apollo/client'; +import { MockedProvider } from '@apollo/client/testing'; +import renderer from 'react-test-renderer'; +import { + ActiveValidatorCountDocument, + AverageBlockTimeDocument, + LatestBlockHeightListenerDocument, + TokenPriceListenerDocument, +} from '@/graphql/types/general_types'; +import DataBlocks from '@/screens/home/components/data_blocks'; +import MockTheme from '@/tests/mocks/MockTheme'; +import wait from '@/tests/utils/wait'; + +// ================================== +// mocks +// ================================== +beforeEach(() => { + jest.mock( + '@/screens/home/components/data_blocks/components/single_block', + () => (props: JSX.IntrinsicElements['div']) =>
+ ); +}); + +const mockLatestBlockHeight = jest.fn().mockReturnValue({ + data: { + height: [ + { + height: 953992, + }, + ], + }, +}); + +const mockAverageBlockTime = jest.fn().mockReturnValue({ + data: { + averageBlockTime: [ + { + averageTime: 6.540624503709312, + }, + ], + }, +}); + +const mockTokenPrice = jest.fn().mockReturnValue({ + data: { + tokenPrice: [], + }, +}); + +const mockActiveValidatorsCount = jest.fn().mockReturnValue({ + data: { + activeTotal: { + aggregate: { + count: 200, + }, + }, + inactiveTotal: { + aggregate: { + count: 332, + }, + }, + total: { + aggregate: { + count: 532, + }, + }, + }, +}); + +// ================================== +// unit tests +// ================================== +describe('screen: Home/DataBlocks', () => { + it('matches snapshot', async () => { + let component: renderer.ReactTestRenderer | undefined; + + renderer.act(() => { + component = renderer.create( + + + + + + + + ); + }); + await wait(renderer.act); + const tree = component?.toJSON(); + expect(tree).toMatchSnapshot(); + expect(mockActiveValidatorsCount).toBeCalledTimes(1); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); +}); diff --git a/apps/web-stratos/src/screens/home/components/data_blocks/index.tsx b/apps/web-stratos/src/screens/home/components/data_blocks/index.tsx new file mode 100644 index 0000000000..f98c2befa5 --- /dev/null +++ b/apps/web-stratos/src/screens/home/components/data_blocks/index.tsx @@ -0,0 +1,57 @@ +import useAppTranslation from '@/hooks/useAppTranslation'; +import numeral from 'numeral'; +import { FC } from 'react'; +import SingleBlock from '@/screens/home/components/data_blocks/components/single_block'; +import { useDataBlocks } from '@/screens/home/components/data_blocks/hooks'; +import useStyles from '@/screens/home/components/data_blocks/styles'; +import CoinGeckoIcon from 'shared-utils/assets/icon-coingecko.svg'; + +const DataBlocks: FC = ({ className }) => { + const { t } = useAppTranslation('home'); + const { classes, cx } = useStyles(); + const { state } = useDataBlocks(); + const data = [ + { + key: t('latestBlock'), + value: numeral(state.blockHeight).format('0,0'), + className: classes.blockHeight, + }, + { + key: t('averageBlockTime'), + value: `${numeral(state.blockTime).format('0.00')} s`, + className: classes.blockTime, + }, + { + key: t('price'), + value: state.price !== null ? `$${numeral(state.price).format('0.000')}` : 'N/A', + description: t('dataFrom'), + Icon: , + className: classes.price, + }, + { + key: t('activeValidators'), + value: numeral(state.validators.active).format('0,0'), + description: t('outOfValidators', { + num: numeral(state.validators.total).format('0,0'), + }), + className: classes.validators, + }, + ]; + + return ( +
+ {data.map((x) => ( + + ))} +
+ ); +}; + +export default DataBlocks; diff --git a/apps/web-stratos/src/screens/home/components/data_blocks/styles.ts b/apps/web-stratos/src/screens/home/components/data_blocks/styles.ts new file mode 100644 index 0000000000..3e8c447952 --- /dev/null +++ b/apps/web-stratos/src/screens/home/components/data_blocks/styles.ts @@ -0,0 +1,30 @@ +import { makeStyles } from 'tss-react/mui'; + +const useStyles = makeStyles()((theme) => ({ + root: { + display: 'grid', + gap: theme.spacing(1), + gridTemplateRows: 'auto', + [theme.breakpoints.up('sm')]: { + gridTemplateColumns: 'repeat(2, 1fr)', + }, + [theme.breakpoints.up('lg')]: { + gap: theme.spacing(2), + gridTemplateColumns: 'repeat(4, 1fr)', + }, + }, + blockHeight: { + background: theme.palette.custom.primaryData.one, + }, + blockTime: { + background: theme.palette.custom.primaryData.two, + }, + price: { + background: theme.palette.custom.primaryData.three, + }, + validators: { + background: theme.palette.custom.primaryData.four, + }, +})); + +export default useStyles; diff --git a/apps/web-stratos/src/screens/home/components/hero/components/index.ts b/apps/web-stratos/src/screens/home/components/hero/components/index.ts new file mode 100644 index 0000000000..5c124f9a06 --- /dev/null +++ b/apps/web-stratos/src/screens/home/components/hero/components/index.ts @@ -0,0 +1,4 @@ +import OnlineVotingPower from '@/screens/home/components/hero/components/online_voting_power'; +import TokenPrice from '@/screens/home/components/hero/components/token_price'; + +export { OnlineVotingPower, TokenPrice }; diff --git a/apps/web-stratos/src/screens/home/components/hero/components/online_voting_power/__snapshots__/index.test.tsx.snap b/apps/web-stratos/src/screens/home/components/hero/components/online_voting_power/__snapshots__/index.test.tsx.snap new file mode 100644 index 0000000000..48323a76a0 --- /dev/null +++ b/apps/web-stratos/src/screens/home/components/hero/components/online_voting_power/__snapshots__/index.test.tsx.snap @@ -0,0 +1,487 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`screen: Home/OnlineVotingPower matches snapshot 1`] = ` +.emotion-0 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; +} + +.emotion-0 .MuiTypography-h2 { + margin-bottom: 16px; +} + +.emotion-1 { + margin: 0; + font-size: 1.5rem; + letter-spacing: 0; + font-family: "Roboto","Helvetica","Arial",sans-serif; + font-weight: 300; + line-height: 1.2; +} + +.emotion-2 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-align-items: flex-end; + -webkit-box-align: flex-end; + -ms-flex-align: flex-end; + align-items: flex-end; +} + +.emotion-2 .primary__data { + color: #FF835B; + margin-right: 16px; + font-size: 2.5rem; +} + +.emotion-3 { + margin: 0; + font-size: 1.25rem; + letter-spacing: 0.15px; + font-family: "Roboto","Helvetica","Arial",sans-serif; + font-weight: 400; + line-height: 1.167; +} + +.emotion-4 { + margin: 0; + font-size: 1rem; + white-space: pre-wrap; + letter-spacing: 0.5px; + font-family: "Roboto","Helvetica","Arial",sans-serif; + font-weight: 400; + line-height: 1.5; +} + +.emotion-5 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + height: 11px; + border-radius: 4px; + background: rgba(255, 131, 91, 0.2); + overflow: hidden; + margin: 16px 0px; +} + +.emotion-6 { + width: 100%; + background: #FF835B; + -webkit-transition: 0.3s; + transition: 0.3s; +} + +@media (min-width:1280px) { + .emotion-7 { + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + -webkit-box-pack: end; + -ms-flex-pack: end; + -webkit-justify-content: flex-end; + justify-content: flex-end; + } +} + +.emotion-8:not(:last-child) { + margin-bottom: 16px; +} + +.emotion-8 .label { + margin-bottom: 8px; + color: #777777; +} + +.emotion-8 p.value { + color: #414141; +} + +.emotion-8 p.value .positive { + color: #2460FA; +} + +.emotion-8 p.value .negative { + color: #E79720; +} + +.emotion-8 a { + color: #4092CD; +} + +@media (min-width:768px) { + .emotion-8 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: justify; + -webkit-justify-content: space-between; + justify-content: space-between; + } +} + +.emotion-9 { + margin: 0; + font-size: 1rem; + letter-spacing: 0.15px; + font-family: "Roboto","Helvetica","Arial",sans-serif; + font-weight: 400; + line-height: 1.235; +} + +
+

+ onlineVotingPower +

+
+

+ 100.00% +

+

+ 76,341,043 + / + + 76,341,043 +

+
+
+
+
+
+
+

+ validators +

+

+ 109 +

+
+
+

+ votingPowerPercent +

+

+ 100.00% +

+
+
+

+ votingPower +

+

+ 76,341,043 +

+
+
+

+ totalVotingPower +

+

+ 76,341,043 +

+
+
+
+`; + +exports[`screen: Home/OnlineVotingPower matches snapshot 2`] = ` +.emotion-0 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; +} + +.emotion-0 .MuiTypography-h2 { + margin-bottom: 16px; +} + +.emotion-1 { + margin: 0; + font-size: 1.5rem; + letter-spacing: 0; + font-family: "Roboto","Helvetica","Arial",sans-serif; + font-weight: 300; + line-height: 1.2; +} + +.emotion-2 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-align-items: flex-end; + -webkit-box-align: flex-end; + -ms-flex-align: flex-end; + align-items: flex-end; +} + +.emotion-2 .primary__data { + color: #FF835B; + margin-right: 16px; + font-size: 2.5rem; +} + +.emotion-3 { + margin: 0; + font-size: 1.25rem; + letter-spacing: 0.15px; + font-family: "Roboto","Helvetica","Arial",sans-serif; + font-weight: 400; + line-height: 1.167; +} + +.emotion-4 { + margin: 0; + font-size: 1rem; + white-space: pre-wrap; + letter-spacing: 0.5px; + font-family: "Roboto","Helvetica","Arial",sans-serif; + font-weight: 400; + line-height: 1.5; +} + +.emotion-5 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + height: 11px; + border-radius: 4px; + background: rgba(255, 131, 91, 0.2); + overflow: hidden; + margin: 16px 0px; +} + +.emotion-6 { + width: 100%; + background: #FF835B; + -webkit-transition: 0.3s; + transition: 0.3s; +} + +@media (min-width:1280px) { + .emotion-7 { + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + -webkit-box-pack: end; + -ms-flex-pack: end; + -webkit-justify-content: flex-end; + justify-content: flex-end; + } +} + +.emotion-8:not(:last-child) { + margin-bottom: 16px; +} + +.emotion-8 .label { + margin-bottom: 8px; + color: #777777; +} + +.emotion-8 p.value { + color: #414141; +} + +.emotion-8 p.value .positive { + color: #2460FA; +} + +.emotion-8 p.value .negative { + color: #E79720; +} + +.emotion-8 a { + color: #4092CD; +} + +@media (min-width:768px) { + .emotion-8 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: justify; + -webkit-justify-content: space-between; + justify-content: space-between; + } +} + +.emotion-9 { + margin: 0; + font-size: 1rem; + letter-spacing: 0.15px; + font-family: "Roboto","Helvetica","Arial",sans-serif; + font-weight: 400; + line-height: 1.235; +} + +
+

+ onlineVotingPower +

+
+

+ 100.00% +

+

+ 76,341,043 + / + + 76,341,043 +

+
+
+
+
+
+
+

+ validators +

+

+ 109 +

+
+
+

+ votingPowerPercent +

+

+ 100.00% +

+
+
+

+ votingPower +

+

+ 76,341,043 +

+
+
+

+ totalVotingPower +

+

+ 76,341,043 +

+
+
+
+`; diff --git a/apps/web-stratos/src/screens/home/components/hero/components/online_voting_power/hooks.ts b/apps/web-stratos/src/screens/home/components/hero/components/online_voting_power/hooks.ts new file mode 100644 index 0000000000..353046f128 --- /dev/null +++ b/apps/web-stratos/src/screens/home/components/hero/components/online_voting_power/hooks.ts @@ -0,0 +1,64 @@ +import numeral from 'numeral'; +import Big from 'big.js'; +import * as R from 'ramda'; +import { useCallback, useState } from 'react'; +import chainConfig from '@/chainConfig'; +import { OnlineVotingPowerQuery, useOnlineVotingPowerQuery } from '@/graphql/types/general_types'; +import { formatToken } from '@/utils/format_token'; + +const { extra, votingPowerTokenUnit } = chainConfig(); + +type OnlineVotingPowerState = { + votingPower: number; + totalVotingPower: number; + activeValidators: number; +}; + +const initialState: OnlineVotingPowerState = { + votingPower: 0, + totalVotingPower: 0, + activeValidators: 0, +}; + +const formatOnlineVotingPower = (data: OnlineVotingPowerQuery) => { + const votingPowerReducted = data?.validatorVotingPowerAggregate?.aggregate?.sum?.votingPower ?? 0; + const bonded = data?.stakingPool?.[0]?.bonded ?? 0; + const activeValidators = data?.activeTotal?.aggregate?.count ?? 0; + const votingPower = Big(votingPowerReducted) + .mul(10 ** (extra.votingPowerExponent ?? 0)) + .toNumber(); + const formattedVotingPower = + numeral(formatToken(votingPower, votingPowerTokenUnit).value).value() ?? 0; + return { + activeValidators, + votingPower: formattedVotingPower, + totalVotingPower: numeral(formatToken(bonded, votingPowerTokenUnit).value).value() ?? 0, + }; +}; + +export const useOnlineVotingPower = () => { + const [state, setState] = useState(initialState); + + const handleSetState = useCallback( + (stateChange: (prevState: OnlineVotingPowerState) => OnlineVotingPowerState) => { + setState((prevState) => { + const newState = stateChange(prevState); + return R.equals(prevState, newState) ? prevState : newState; + }); + }, + [] + ); + + useOnlineVotingPowerQuery({ + onCompleted: (data) => { + handleSetState((prevState) => ({ + ...prevState, + ...formatOnlineVotingPower(data), + })); + }, + }); + + return { + state, + }; +}; diff --git a/apps/web-stratos/src/screens/home/components/hero/components/online_voting_power/index.test.tsx b/apps/web-stratos/src/screens/home/components/hero/components/online_voting_power/index.test.tsx new file mode 100644 index 0000000000..1fbf9f4a2a --- /dev/null +++ b/apps/web-stratos/src/screens/home/components/hero/components/online_voting_power/index.test.tsx @@ -0,0 +1,84 @@ +import { OnlineVotingPowerDocument } from '@/graphql/types/general_types'; +import OnlineVotingPower from '@/screens/home/components/hero/components/online_voting_power'; +import { mockClient } from '@/tests/mocks/mockApollo'; +import MockTheme from '@/tests/mocks/MockTheme'; +import wait from '@/tests/utils/wait'; +import { ApolloProvider } from '@apollo/client'; +import { MockedProvider } from '@apollo/client/testing'; +import renderer from 'react-test-renderer'; + +// ================================== +// mocks +// ================================== +jest.mock('@/components/box', () => (props: JSX.IntrinsicElements['div']) => ( +
+)); + +const mockOnlineVotingPower = jest.fn().mockReturnValue({ + data: { + activeTotal: { + aggregate: { + count: 109, + }, + }, + validatorVotingPowerAggregate: { + aggregate: { + sum: { + votingPower: 76341043, + }, + }, + }, + stakingPool: [ + { + bonded: 76341043, + }, + ], + stakingParams: [ + { + params: { + bond_denom: 'udsm', + max_entries: 7, + max_validators: 125, + unbonding_time: 1209600000000000, + historical_entries: 10000, + }, + }, + ], + }, +}); + +// ================================== +// unit tests +// ================================== +describe('screen: Home/OnlineVotingPower', () => { + it('matches snapshot', async () => { + let component: renderer.ReactTestRenderer | undefined; + + renderer.act(() => { + component = renderer.create( + + + + + + + + ); + }); + await wait(renderer.act); + + let tree = component?.toJSON(); + expect(tree).toMatchSnapshot(); + + tree = component?.toJSON(); + expect(tree).toMatchSnapshot(); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); +}); diff --git a/apps/web-stratos/src/screens/home/components/hero/components/online_voting_power/index.tsx b/apps/web-stratos/src/screens/home/components/hero/components/online_voting_power/index.tsx new file mode 100644 index 0000000000..4d1650fbdd --- /dev/null +++ b/apps/web-stratos/src/screens/home/components/hero/components/online_voting_power/index.tsx @@ -0,0 +1,82 @@ +import Typography from '@mui/material/Typography'; +import useAppTranslation from '@/hooks/useAppTranslation'; +import numeral from 'numeral'; +import { FC } from 'react'; +import chainConfig from '@/chainConfig'; +import useStyles from '@/screens/home/components/hero/components/online_voting_power/styles'; +import { useOnlineVotingPower } from '@/screens/home/components/hero/components/online_voting_power/hooks'; + +const OnlineVotingPower: FC = () => { + const { t } = useAppTranslation('home'); + const { state } = useOnlineVotingPower(); + + const { chainName } = chainConfig(); + + const votingPowerPercent = + // eslint-disable-next-line no-nested-ternary + chainName === 'wormhole' + ? numeral((state.activeValidators / state.votingPower) * 100) + : state.totalVotingPower === 0 + ? numeral(0) + : numeral((state.votingPower / state.totalVotingPower) * 100); + + const { classes } = useStyles({ percentage: votingPowerPercent.format('0') }); + + return ( +
+ {t('onlineVotingPower')} +
+ + {`${votingPowerPercent.format('0,0.00', (n) => Math.floor(n))}%`} + + + {numeral(state.votingPower).format('0,0')} /{' '} + {state.totalVotingPower === 0 + ? numeral(state.votingPower).format('0,0') + : numeral(state.totalVotingPower).format('0,0')} + +
+
+
+
+
+
+ + {t('validators')} + + + {numeral(state.activeValidators).format('0,0')} + +
+
+ + {t('votingPowerPercent')} + + + {`${votingPowerPercent.format('0,0.00', (n) => Math.floor(n))}%`} + +
+
+ + {t('votingPower')} + + + {numeral(state.votingPower).format('0,0')} + +
+ {state.totalVotingPower === 0 ? null : ( +
+ + {t('totalVotingPower')} + + + {numeral(state.totalVotingPower).format('0,0')} + +
+ )} +
+
+ ); +}; + +export default OnlineVotingPower; diff --git a/apps/web-stratos/src/screens/home/components/hero/components/online_voting_power/styles.ts b/apps/web-stratos/src/screens/home/components/hero/components/online_voting_power/styles.ts new file mode 100644 index 0000000000..df04a6a9eb --- /dev/null +++ b/apps/web-stratos/src/screens/home/components/hero/components/online_voting_power/styles.ts @@ -0,0 +1,71 @@ +import { makeStyles } from 'tss-react/mui'; +import Color from 'color'; + +const useStyles = makeStyles<{ percentage: number | string }>()((theme, { percentage }) => ({ + root: { + display: 'flex', + flexDirection: 'column', + '& .MuiTypography-h2': { + marginBottom: theme.spacing(2), + }, + }, + data: { + display: 'flex', + alignItems: 'flex-end', + '& .primary__data': { + color: theme.palette.primary.main, + marginRight: theme.spacing(2), + fontSize: '2.5rem', + }, + }, + chart: { + display: 'flex', + height: '11px', + borderRadius: theme.shape.borderRadius, + background: Color(theme.palette.primary.main).alpha(0.2).string(), + overflow: 'hidden', + margin: theme.spacing(2, 0), + }, + active: { + width: `${percentage}%`, + background: theme.palette.primary.main, + transition: '0.3s', + }, + itemsContainer: { + [theme.breakpoints.up('lg')]: { + flex: 1, + display: 'flex', + flexDirection: 'column', + justifyContent: 'flex-end', + }, + }, + item: { + '&:not(:last-child)': { + marginBottom: theme.spacing(2), + }, + '& .label': { + marginBottom: theme.spacing(1), + color: theme.palette.custom.fonts.fontThree, + }, + '& p.value': { + color: theme.palette.custom.fonts.fontTwo, + '& .positive': { + color: theme.palette.custom.tags.one, + }, + '& .negative': { + color: theme.palette.custom.tags.three, + }, + }, + '& a': { + color: theme.palette.custom.fonts.highlight, + }, + + [theme.breakpoints.up('md')]: { + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + }, + }, +})); + +export default useStyles; diff --git a/apps/web-stratos/src/screens/home/components/hero/components/token_price/hooks.ts b/apps/web-stratos/src/screens/home/components/hero/components/token_price/hooks.ts new file mode 100644 index 0000000000..a9e0044037 --- /dev/null +++ b/apps/web-stratos/src/screens/home/components/hero/components/token_price/hooks.ts @@ -0,0 +1,191 @@ +import numeral from 'numeral'; +import dayjs, { formatDayJs } from '@/utils/dayjs'; +import { useMemo, useRef, useEffect } from 'react'; +import { + createChart, + LineStyle, + PriceScaleMode, + ColorType, + UTCTimestamp, + TimeScaleOptions, + Time, +} from 'lightweight-charts'; +import { useRecoilValue } from 'recoil'; +import type { Theme } from '@mui/material'; +import { readDate, readTimeFormat } from '@/recoil/settings'; +import type { TokenPriceType } from '@/screens/home/components/hero/types'; +import useAppTranslation from '@/hooks/useAppTranslation'; + +const formatTime = (time: dayjs.Dayjs, mode: 'locale' | 'utc' = 'locale') => { + if (mode === 'utc') { + return time.unix(); + } + return time.local().unix(); +}; + +const tickTimeFormatter = ( + unix: UTCTimestamp, + mode: 'locale' | 'utc', + format: '12-hour' | '24-hour', + year: boolean + // eslint-disable-next-line consistent-return +): string => { + const dateObj = dayjs.unix(unix); + const utc12FullFormat = 'YYYY-MM-DD hh:mm:ss A [(UTC)]'; + const utc24FullFormat = 'YYYY-MM-DD HH:mm:ss'; + const local12FullFormat = 'YYYY-MM-DD hh:mm:ss A (z)'; + const local24FullFormat = 'YYYY-MM-DD HH:mm:ss (z)'; + + const t12Format = 'hh:mm A'; + const t24Format = 'HH:mm'; + + if (year) { + if (format === '24-hour') { + if (mode === 'utc') { + return dateObj.utc().format(utc24FullFormat); + } + return dateObj.local().format(local24FullFormat); + } + if (format === '12-hour') { + if (mode === 'utc') { + return dateObj.utc().format(utc12FullFormat); + } + return dateObj.local().format(local12FullFormat); + } + } else { + if (format === '24-hour') { + if (mode === 'utc') { + return dateObj.utc().format(t24Format); + } + return dateObj.local().format(t24Format); + } + if (format === '12-hour') { + if (mode === 'utc') { + return dateObj.utc().format(t12Format); + } + return dateObj.local().format(t12Format); + } + } + throw new Error('Invalid parameters'); +}; + +const tickPriceFormatter = (num: number) => Number(numeral(num).format('0,0.00000')); + +export const usePrice = (items: TokenPriceType[], theme: Theme) => { + const { i18n } = useAppTranslation('home'); + + const dateFormat = useRecoilValue(readDate); + const timeFormat = useRecoilValue(readTimeFormat); + + const formatItems = useMemo( + () => + items.map((x) => ({ + time: formatTime(dayjs.utc(x.time), dateFormat) as UTCTimestamp, + fullTime: formatDayJs(dayjs.utc(x.time), dateFormat, timeFormat), + value: x.value, + })), + [items, dateFormat, timeFormat] + ); + + const chartRef = useRef(null); + + useEffect(() => { + const container = chartRef.current; + if (!container) return; + const chart = createChart(container, { + width: container.clientWidth, + height: container.clientHeight, + localization: { + dateFormat: 'yyyy/MM/dd', + locale: i18n.language === 'en' ? 'en-US' : navigator.language, + priceFormatter: (p: number) => `${tickPriceFormatter(p)}`, + timeFormatter: (t: UTCTimestamp) => + tickTimeFormatter(t as UTCTimestamp, dateFormat, timeFormat, true), + }, + autoSize: true, + }); + chart.timeScale().fitContent(); + + const lineSeries = chart.addLineSeries({ + color: theme.palette.custom.primaryData.one, + lineWidth: 2, + priceScaleId: 'right', + }); + + lineSeries.setData(formatItems); + + const handleResize = () => { + chart.applyOptions({ width: container.clientWidth }); + }; + + chart.priceScale('right').applyOptions({ + mode: PriceScaleMode.Normal, + borderColor: theme.palette.divider, + autoScale: true, + ticksVisible: true, + textColor: theme.palette.text.primary, + scaleMargins: { + top: 0.1, + bottom: 0.1, + }, + }); + + const timeScaleOptions: TimeScaleOptions = { + timeVisible: true, + secondsVisible: true, + shiftVisibleRangeOnNewBar: true, + rightOffset: 10, + fixLeftEdge: true, + fixRightEdge: true, + borderVisible: true, + borderColor: theme.palette.divider, + lockVisibleTimeRangeOnResize: true, + tickMarkFormatter: (time: Time) => + tickTimeFormatter(time as UTCTimestamp, dateFormat, timeFormat, false), + barSpacing: 0, + minBarSpacing: 0, + rightBarStaysOnScroll: true, + visible: true, + ticksVisible: true, + }; + + chart.applyOptions({ + layout: { + background: { type: ColorType.Solid, color: theme.palette.background.paper }, + textColor: theme.palette.text.primary, + }, + grid: { + vertLines: { + color: theme.palette.divider, + style: LineStyle.Dotted, + }, + horzLines: { + color: theme.palette.divider, + style: LineStyle.Dotted, + }, + }, + timeScale: timeScaleOptions, + }); + + window.addEventListener('resize', handleResize); + + // eslint-disable-next-line consistent-return + return () => { + window.removeEventListener('resize', handleResize); + chart.remove(); + }; + }, [ + dateFormat, + formatItems, + i18n.language, + theme.palette.background.paper, + theme.palette.custom.primaryData.one, + theme.palette.divider, + theme.palette.text.primary, + timeFormat, + ]); + + return { + chartRef, + }; +}; diff --git a/apps/web-stratos/src/screens/home/components/hero/components/token_price/index.tsx b/apps/web-stratos/src/screens/home/components/hero/components/token_price/index.tsx new file mode 100644 index 0000000000..858dbcb132 --- /dev/null +++ b/apps/web-stratos/src/screens/home/components/hero/components/token_price/index.tsx @@ -0,0 +1,21 @@ +import { usePrice } from '@/screens/home/components/hero/components/token_price/hooks'; +import useStyles from '@/screens/home/components/hero/components/token_price/styles'; +import type { TokenPriceType } from '@/screens/home/components/hero/types'; +import Typography from '@mui/material/Typography'; +import useAppTranslation from '@/hooks/useAppTranslation'; +import { FC } from 'react'; + +const TokenPrice: FC<{ items: TokenPriceType[] }> = (props) => { + const { classes, theme } = useStyles(); + const { t } = useAppTranslation('home'); + const { chartRef } = usePrice(props.items, theme); + + return ( +
+ {t('priceHistory')} +
+
+ ); +}; + +export default TokenPrice; diff --git a/apps/web-stratos/src/screens/home/components/hero/components/token_price/styles.ts b/apps/web-stratos/src/screens/home/components/hero/components/token_price/styles.ts new file mode 100644 index 0000000000..90b9c09746 --- /dev/null +++ b/apps/web-stratos/src/screens/home/components/hero/components/token_price/styles.ts @@ -0,0 +1,10 @@ +import { makeStyles } from 'tss-react/mui'; + +const useStyles = makeStyles()(() => ({ + chart: { + height: '285px', + width: '100%', + }, +})); + +export default useStyles; diff --git a/apps/web-stratos/src/screens/home/components/hero/hooks.ts b/apps/web-stratos/src/screens/home/components/hero/hooks.ts new file mode 100644 index 0000000000..0ec6f2e8c8 --- /dev/null +++ b/apps/web-stratos/src/screens/home/components/hero/hooks.ts @@ -0,0 +1,52 @@ +import * as R from 'ramda'; +import { useCallback, useState } from 'react'; +import chainConfig from '@/chainConfig'; +import { useTokenPriceHistoryQuery } from '@/graphql/types/general_types'; +import type { HeroState } from '@/screens/home/components/hero/types'; + +const { primaryTokenUnit, tokenUnits } = chainConfig(); + +export const useHero = () => { + const [state, setState] = useState({ + loading: true, + exists: true, + tokenPriceHistory: [], + }); + + const handleSetState = useCallback((stateChange: (prevState: HeroState) => HeroState) => { + setState((prevState) => { + const newState = stateChange(prevState); + return R.equals(prevState, newState) ? prevState : newState; + }); + }, []); + + useTokenPriceHistoryQuery({ + variables: { + limit: 48, + denom: tokenUnits?.[primaryTokenUnit]?.display, + }, + onCompleted: (data) => { + handleSetState((prevState) => { + const newState = { + ...prevState, + loading: false, + tokenPriceHistory: + data.tokenPrice.length === 48 + ? [...data.tokenPrice].reverse().map((x) => ({ + time: x.timestamp, + value: x.price, + })) + : prevState.tokenPriceHistory, + }; + return R.equals(prevState, newState) ? prevState : newState; + }); + }, + onError: () => { + handleSetState((prevState) => ({ ...prevState, loading: false })); + }, + }); + + return { + state, + }; +}; diff --git a/apps/web-stratos/src/screens/home/components/hero/index.tsx b/apps/web-stratos/src/screens/home/components/hero/index.tsx new file mode 100644 index 0000000000..d34771b6bc --- /dev/null +++ b/apps/web-stratos/src/screens/home/components/hero/index.tsx @@ -0,0 +1,24 @@ +import Box from '@/components/box'; +import Loading from '@/components/loading'; +import OnlineVotingPower from '@/screens/home/components/hero/components/online_voting_power'; +import TokenPrice from '@/screens/home/components/hero/components/token_price'; +import { useHero } from '@/screens/home/components/hero/hooks'; +import { FC } from 'react'; + +const Hero: FC = (props) => { + const { state } = useHero(); + let component = null; + if (!state.loading) { + if (state.tokenPriceHistory.length) { + component = ; + } else { + component = ; + } + } else { + component = ; + } + + return {component}; +}; + +export default Hero; diff --git a/apps/web-stratos/src/screens/home/components/hero/types.ts b/apps/web-stratos/src/screens/home/components/hero/types.ts new file mode 100644 index 0000000000..e7a3668bb1 --- /dev/null +++ b/apps/web-stratos/src/screens/home/components/hero/types.ts @@ -0,0 +1,10 @@ +export interface TokenPriceType { + time: string; + value: number; +} + +export interface HeroState { + loading: boolean; + exists: boolean; + tokenPriceHistory: TokenPriceType[]; +} diff --git a/apps/web-stratos/src/screens/home/components/index.ts b/apps/web-stratos/src/screens/home/components/index.ts new file mode 100644 index 0000000000..335f1e20d1 --- /dev/null +++ b/apps/web-stratos/src/screens/home/components/index.ts @@ -0,0 +1,8 @@ +import Consensus from '@/screens/home/components/consensus'; +import DataBlocks from '@/screens/home/components/data_blocks'; +import Tokenomics from '@/screens/home/components/tokenomics'; +import Blocks from '@/screens/home/components/blocks'; +import Transactions from '@/screens/home/components/transactions'; +import Hero from '@/screens/home/components/hero'; + +export { DataBlocks, Consensus, Tokenomics, Blocks, Transactions, Hero }; diff --git a/apps/web-stratos/src/screens/home/components/tokenomics/__snapshots__/index.test.tsx.snap b/apps/web-stratos/src/screens/home/components/tokenomics/__snapshots__/index.test.tsx.snap new file mode 100644 index 0000000000..5e3625603f --- /dev/null +++ b/apps/web-stratos/src/screens/home/components/tokenomics/__snapshots__/index.test.tsx.snap @@ -0,0 +1,219 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`screen: Home/Tokenomics matches snapshot 1`] = ` +.emotion-0 { + height: 100%; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-pack: center; + -ms-flex-pack: center; + -webkit-justify-content: center; + justify-content: center; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; +} + +.emotion-1 { + margin: 0; + font-size: 1.5rem; + letter-spacing: 0; + font-family: "Roboto","Helvetica","Arial",sans-serif; + font-weight: 300; + line-height: 1.2; + margin-bottom: 16px; +} + +.emotion-2 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; +} + +.emotion-2 .data__item { + width: 50%; + white-space: pre-wrap; +} + +.emotion-2 .data__item h4 { + color: #414141; +} + +.emotion-2 .data__item .MuiTypography-caption { + color: #777777; +} + +.emotion-3 { + margin: 0; + font-size: 1rem; + letter-spacing: 0.15px; + font-family: "Roboto","Helvetica","Arial",sans-serif; + font-weight: 400; + line-height: 1.235; +} + +.emotion-4 { + margin: 0; + font-size: 0.75rem; + letter-spacing: 0.4px; + font-family: "Roboto","Helvetica","Arial",sans-serif; + font-weight: 400; + line-height: 1.66; +} + +.emotion-7 { + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: space-around; + -ms-flex-pack: space-around; + -webkit-justify-content: space-around; + justify-content: space-around; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; +} + +.emotion-8 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-align-items: flex-start; + -webkit-box-align: flex-start; + -ms-flex-align: flex-start; + align-items: flex-start; + -webkit-box-pack: start; + -ms-flex-pack: start; + -webkit-justify-content: flex-start; + justify-content: flex-start; + -webkit-box-flex-wrap: wrap; + -webkit-flex-wrap: wrap; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + width: 100%; +} + +.emotion-8 .MuiTypography-caption { + color: #777777; +} + +.emotion-8 .legends__item { + width: 50%; +} + +.emotion-8 .legends__item:before { + content: ""; + display: inline-block; + width: 12px; + height: 12px; + margin-right: 5px; +} + +.emotion-8 .legends__item:first-of-type:before { + background: #2FB6E0; +} + +.emotion-8 .legends__item:nth-of-type(2):before { + background: #FFC93D; +} + +.emotion-8 .legends__item:last-child:before { + background: #20D494; +} + +.emotion-8 .legends__item .caption__percent { + color: #777777; +} + +
+

+ tokenomics +

+
+
+

+ 254,578,529,800 + +

+ + bondedPercent + +
+
+

+ -459,465,964,998 + +

+ + unbondedPercent + +
+
+
+
+
+ + bonded + +
+
+ + unbonded + +
+
+ + unbonding + +
+
+
+
+`; diff --git a/apps/web-stratos/src/screens/home/components/tokenomics/hooks.ts b/apps/web-stratos/src/screens/home/components/tokenomics/hooks.ts new file mode 100644 index 0000000000..b9905416cd --- /dev/null +++ b/apps/web-stratos/src/screens/home/components/tokenomics/hooks.ts @@ -0,0 +1,49 @@ +import { useMemo } from 'react'; +import { useQuery } from '@tanstack/react-query'; +import { ethers } from 'ethers'; +import { getMetrics } from './queries'; + +type TokenomicsState = { + total: ethers.BigNumber; + bonded: ethers.BigNumber; + unbonded: ethers.BigNumber; + unbonding: ethers.BigNumber; + totalMiningSupply: ethers.BigNumber; + totalMinedTokens: ethers.BigNumber; + toBeMined: ethers.BigNumber; + circulationSupply: ethers.BigNumber; + miningReward: ethers.BigNumber; + resourceNodesDeposit: ethers.BigNumber; +}; + +export const useTokenomics = () => { + const { isLoading, error, data } = useQuery({ + queryKey: ['metrics'], + queryFn: getMetrics, + }); + + const tokenomics = useMemo((): TokenomicsState | undefined => { + if (!data) return undefined; + + const metrics = { + total: ethers.utils.parseUnits(data.total_supply, 'wei'), + bonded: ethers.utils.parseUnits(data.total_bonded_delegation, 'wei'), + unbonded: ethers.utils.parseUnits(data.total_unbonded_delegation, 'wei'), + unbonding: ethers.utils.parseUnits(data.total_unbonding_delegation, 'wei'), + totalMiningSupply: ethers.utils.parseUnits(data.total_mining_supply, 'wei'), + totalMinedTokens: ethers.utils.parseUnits(data.total_mined_tokens, 'wei'), + toBeMined: ethers.utils.parseUnits('0', 'wei'), + circulationSupply: ethers.utils.parseUnits(data.circulation_supply, 'wei'), + miningReward: ethers.utils.parseUnits(data.chain_mining_reward, 'wei'), + resourceNodesDeposit: ethers.utils.parseUnits(data.total_resource_nodes_deposit, 'wei'), + } as TokenomicsState; + metrics.toBeMined = metrics.totalMiningSupply.sub(metrics.totalMinedTokens); + return metrics; + }, [data]); + + return { + data: tokenomics, + isLoading, + error, + }; +}; diff --git a/apps/web-stratos/src/screens/home/components/tokenomics/index.test.tsx b/apps/web-stratos/src/screens/home/components/tokenomics/index.test.tsx new file mode 100644 index 0000000000..fed5485f48 --- /dev/null +++ b/apps/web-stratos/src/screens/home/components/tokenomics/index.test.tsx @@ -0,0 +1,87 @@ +import { TokenomicsDocument } from '@/graphql/types/general_types'; +import Tokenomics from '@/screens/home/components/tokenomics'; +import { mockClient } from '@/tests/mocks/mockApollo'; +import MockTheme from '@/tests/mocks/MockTheme'; +import wait from '@/tests/utils/wait'; +import { ApolloProvider } from '@apollo/client'; +import { MockedProvider } from '@apollo/client/testing'; +import renderer from 'react-test-renderer'; + +// ================================== +// mocks +// ================================== +jest.mock('@/components/box', () => (props: JSX.IntrinsicElements['div']) => ( +
+)); +// to fix error, this.wrapperNode is null node_modules/recharts/src/component/Tooltip.tsx:143 +jest.mock('recharts', () => ({ + ...jest.requireActual('recharts'), + Tooltip: () =>
, +})); + +const mockTokenomics = jest.fn().mockReturnValue({ + data: { + stakingParams: [ + { + params: { + bond_denom: 'udsm', + max_entries: 7, + max_validators: 125, + unbonding_time: 1209600000000000, + historical_entries: 10000, + }, + }, + ], + stakingPool: [ + { + bonded: 254578529800, + unbonded: 204887435198, + }, + ], + supply: [ + { + coins: [ + { + denom: 'udaric', + amount: '7987725829900', + }, + { + denom: 'upotin', + amount: '80000000000000', + }, + ], + }, + ], + }, +}); + +// ================================== +// unit tests +// ================================== +describe('screen: Home/Tokenomics', () => { + it('matches snapshot', async () => { + let component: renderer.ReactTestRenderer | undefined; + + renderer.act(() => { + component = renderer.create( + + + + + + + + ); + }); + await wait(renderer.act); + + const tree = component?.toJSON(); + expect(tree).toMatchSnapshot(); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); +}); diff --git a/apps/web-stratos/src/screens/home/components/tokenomics/index.tsx b/apps/web-stratos/src/screens/home/components/tokenomics/index.tsx new file mode 100644 index 0000000000..8885cdac27 --- /dev/null +++ b/apps/web-stratos/src/screens/home/components/tokenomics/index.tsx @@ -0,0 +1,98 @@ +import { FC } from 'react'; +import numeral from 'numeral'; +import dynamic from 'next/dynamic'; + +import chainConfig from '@/chainConfig'; +import Box from '@/components/box'; +import { useTokenomics } from '@/screens/home/components/tokenomics/hooks'; +import useStyles from '@/screens/home/components/tokenomics/styles'; +import Typography from '@mui/material/Typography'; +import useAppTranslation from '@/hooks/useAppTranslation'; +import { prettyFormat } from './utils'; + +const { tokenUnits } = chainConfig(); + +const TokenomicChart = dynamic(() => import('../charts/TokenomicChart'), { ssr: false }); + +const Tokenomics: FC = ({ className }) => { + const { t } = useAppTranslation('home'); + const { classes, cx, theme } = useStyles(); + const { data: tokenomics, isLoading } = useTokenomics(); + + if (isLoading || !tokenomics) return null; + + const data = [ + { + legendKey: t('bonded'), + percentKey: 'bondedPercent', + value: prettyFormat(tokenomics.bonded, 18, 2), + percent: `${prettyFormat(tokenomics.bonded.mul(1_000_000).div(tokenomics.total), 4, 6)}%`, + fill: theme.palette.custom.tokenomics.one, + }, + { + legendKey: t('toBeMined'), + percentKey: 'toBeMinedPercent', + value: prettyFormat(tokenomics.toBeMined, 18, 2), + percent: `${prettyFormat(tokenomics.toBeMined.mul(1_000_000).div(tokenomics.total), 4, 6)}%`, + fill: theme.palette.custom.tokenomics.two, + }, + { + legendKey: t('deposit'), + percentKey: 'depositPercent', + value: prettyFormat(tokenomics.resourceNodesDeposit, 18, 2), + percent: `${prettyFormat( + tokenomics.resourceNodesDeposit.mul(1_000_000).div(tokenomics.total), + 4, + 6 + )}%`, + fill: theme.palette.custom.tokenomics.three, + }, + { + legendKey: t('unbonded'), + percentKey: 'unbondedPercent', + value: prettyFormat(tokenomics.unbonded, 18, 2), + percent: `${prettyFormat(tokenomics.unbonded.mul(1_000_000).div(tokenomics.total), 4, 6)}%`, + fill: theme.palette.custom.tokenomics.four, + }, + { + legendKey: t('circulationSupply'), + percentKey: 'circulationSupplyPercent', + value: prettyFormat(tokenomics.circulationSupply, 18, 2), + percent: `${prettyFormat( + tokenomics.circulationSupply.mul(1_000_000).div(tokenomics.total), + 4, + 6 + )}%`, + fill: theme.palette.custom.tokenomics.five, + }, + ]; + + return ( + + + {t('tokenomics')} + +
+ {data.map((x) => ( +
+ + {x.value.toLocaleString()} {tokenUnits?.wei?.display?.toUpperCase()} + + + {x.percentKey + ? t(x.percentKey, { + percent: x.percent, + }) + : ''} + +
+ ))} +
+
+ +
+
+ ); +}; + +export default Tokenomics; diff --git a/apps/web-stratos/src/screens/home/components/tokenomics/queries.ts b/apps/web-stratos/src/screens/home/components/tokenomics/queries.ts new file mode 100644 index 0000000000..0890459a6b --- /dev/null +++ b/apps/web-stratos/src/screens/home/components/tokenomics/queries.ts @@ -0,0 +1,6 @@ +import axios from 'axios'; + +export const getMetrics = async () => { + const result = await axios.get(`${process.env.NEXT_PUBLIC_REST_CHAIN_ENDPOINT}/pot/metrics`); + return result.data.result; +}; diff --git a/apps/web-stratos/src/screens/home/components/tokenomics/styles.ts b/apps/web-stratos/src/screens/home/components/tokenomics/styles.ts new file mode 100644 index 0000000000..e61093d162 --- /dev/null +++ b/apps/web-stratos/src/screens/home/components/tokenomics/styles.ts @@ -0,0 +1,73 @@ +import { makeStyles } from 'tss-react/mui'; + +const useStyles = makeStyles()((theme) => ({ + root: { + height: '100%', + display: 'flex', + justifyContent: 'center', + flexDirection: 'column', + }, + label: { + marginBottom: theme.spacing(2), + }, + data: { + display: 'flex', + '& .data__item': { + width: '50%', + whiteSpace: 'pre-wrap', + '& h4': { + color: theme.palette.custom.fonts.fontTwo, + }, + '& .MuiTypography-caption': { + color: theme.palette.custom.fonts.fontThree, + }, + }, + }, + legends: { + display: 'flex', + alignItems: 'flex-start', + justifyContent: 'flex-start', + flexWrap: 'wrap', + width: '100%', + '& .MuiTypography-caption': { + color: theme.palette.custom.fonts.fontThree, + }, + '& .legends__item': { + width: '50%', + '&:before': { + content: '""', + display: 'inline-block', + width: '12px', + height: '12px', + marginRight: '5px', + }, + '&:first-of-type:before': { + background: theme.palette.custom.tokenomics.one, + }, + '&:nth-of-type(2):before': { + background: theme.palette.custom.tokenomics.two, + }, + '&:nth-of-type(3):before': { + background: theme.palette.custom.tokenomics.three, + }, + '&:nth-of-type(4):before': { + background: theme.palette.custom.tokenomics.four, + }, + '&:last-child:before': { + background: theme.palette.custom.tokenomics.five, + }, + '& .caption__percent': { + color: theme.palette.custom.fonts.fontThree, + }, + }, + }, + content: { + flex: 1, + display: 'flex', + alignItems: 'center', + justifyContent: 'space-around', + flexDirection: 'column', + }, +})); + +export default useStyles; diff --git a/apps/web-stratos/src/screens/home/components/tokenomics/utils.ts b/apps/web-stratos/src/screens/home/components/tokenomics/utils.ts new file mode 100644 index 0000000000..3867fd6ffc --- /dev/null +++ b/apps/web-stratos/src/screens/home/components/tokenomics/utils.ts @@ -0,0 +1,6 @@ +import { BigNumberish } from 'ethers'; +import { formatUnits } from 'ethers/lib/utils'; + +export function prettyFormat(value: BigNumberish, unitName?: number, points = 8): number { + return +(+formatUnits(value, unitName)).toFixed(points); +} diff --git a/apps/web-stratos/src/screens/home/components/transactions/__snapshots__/index.test.tsx.snap b/apps/web-stratos/src/screens/home/components/transactions/__snapshots__/index.test.tsx.snap new file mode 100644 index 0000000000..cf14448e46 --- /dev/null +++ b/apps/web-stratos/src/screens/home/components/transactions/__snapshots__/index.test.tsx.snap @@ -0,0 +1,170 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`screen: Home/Blocks/Mobile matches snapshot 1`] = ` +.emotion-0 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; +} + +.emotion-0 a { + color: #4092CD; +} + +.emotion-0 .button { + color: #414141; +} + +.emotion-0 .button:hover { + cursor: pointer; +} + +.emotion-1 { + margin-bottom: 16px; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: justify; + -webkit-justify-content: space-between; + justify-content: space-between; +} + +.emotion-2 { + margin: 0; + font-size: 1.5rem; + letter-spacing: 0; + font-family: "Roboto","Helvetica","Arial",sans-serif; + font-weight: 300; + line-height: 1.2; +} + +@media (max-width:1279.95px) { + .emotion-3 { + display: none; + } +} + +@media (min-width:1280px) { + .emotion-4 { + display: none; + } +} + +.emotion-5 { + margin: 0; + -webkit-flex-shrink: 0; + -ms-flex-negative: 0; + flex-shrink: 0; + border-width: 0; + border-style: solid; + border-color: #E8E8E8; + border-bottom-width: thin; +} + +@media (min-width:1280px) { + .emotion-5 { + display: none; + } +} + +.emotion-6 { + padding-top: 16px; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: center; + -ms-flex-pack: center; + -webkit-justify-content: center; + justify-content: center; +} + +@media (min-width:1280px) { + .emotion-6 { + display: none; + } +} + +
+
+

+ latestTransactions +

+ + seeMore + +
+
+ +`; diff --git a/apps/web-stratos/src/screens/home/components/transactions/components/desktop/__snapshots__/index.test.tsx.snap b/apps/web-stratos/src/screens/home/components/transactions/components/desktop/__snapshots__/index.test.tsx.snap new file mode 100644 index 0000000000..7af9d4d99f --- /dev/null +++ b/apps/web-stratos/src/screens/home/components/transactions/components/desktop/__snapshots__/index.test.tsx.snap @@ -0,0 +1,401 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`screen: Home/Transactions/Desktop matches snapshot 1`] = ` +.emotion-0 { + overflow: auto; +} + +.emotion-1 { + display: table; + width: 100%; + border-collapse: collapse; + border-spacing: 0; +} + +.emotion-1 caption { + font-size: 0.875rem; + letter-spacing: 0.25px; + font-family: "Roboto","Helvetica","Arial",sans-serif; + font-weight: 400; + line-height: 1.43; + padding: 16px; + color: #414141; + text-align: left; + caption-side: bottom; +} + +.emotion-1 .MuiTableBody-root .MuiTableCell-root { + white-space: nowrap; + height: auto; +} + +.emotion-2 { + display: table-header-group; +} + +.emotion-2 { + background-color: initial; +} + +.emotion-3 { + color: inherit; + display: table-row; + vertical-align: middle; + outline: 0; +} + +.emotion-3.MuiTableRow-hover:hover { + background-color: rgba(0, 0, 0, 0.04); +} + +.emotion-3.Mui-selected { + background-color: rgba(255, 131, 91, 0.08); +} + +.emotion-3.Mui-selected:hover { + background-color: rgba(255, 131, 91, 0.12); +} + +.emotion-4 { + font-size: 0.875rem; + letter-spacing: 0.25px; + font-family: "Roboto","Helvetica","Arial",sans-serif; + font-weight: 500; + line-height: 1.5rem; + display: table-cell; + vertical-align: inherit; + border-bottom: 1px solid rgba(252, 252, 252, 1); + text-align: left; + padding: 16px; + color: #000000; +} + +.emotion-4 { + border-bottom: none; + padding: 0 16px; + height: 50px; + font-size: 1rem; +} + +.emotion-7 { + font-size: 0.875rem; + letter-spacing: 0.25px; + font-family: "Roboto","Helvetica","Arial",sans-serif; + font-weight: 500; + line-height: 1.5rem; + display: table-cell; + vertical-align: inherit; + border-bottom: 1px solid rgba(252, 252, 252, 1); + text-align: right; + padding: 16px; + color: #000000; + -webkit-flex-direction: row-reverse; + -ms-flex-direction: row-reverse; + flex-direction: row-reverse; +} + +.emotion-7 { + border-bottom: none; + padding: 0 16px; + height: 50px; + font-size: 1rem; +} + +.emotion-9 { + display: table-row-group; +} + +.emotion-9 .MuiTableRow-root:nth-of-type(odd) { + background-color: #F8F8F8; +} + +.emotion-9 .MuiTableCell-root { + color: #414141; +} + +.emotion-11 { + font-size: 0.875rem; + letter-spacing: 0.25px; + font-family: "Roboto","Helvetica","Arial",sans-serif; + font-weight: 400; + line-height: 1.43; + display: table-cell; + vertical-align: inherit; + border-bottom: 1px solid rgba(252, 252, 252, 1); + text-align: left; + padding: 16px; + color: #000000; +} + +.emotion-11 { + border-bottom: none; + padding: 0 16px; + height: 50px; + font-size: 1rem; +} + +.emotion-14 { + font-size: 0.875rem; + letter-spacing: 0.25px; + font-family: "Roboto","Helvetica","Arial",sans-serif; + font-weight: 400; + line-height: 1.43; + display: table-cell; + vertical-align: inherit; + border-bottom: 1px solid rgba(252, 252, 252, 1); + text-align: right; + padding: 16px; + color: #000000; + -webkit-flex-direction: row-reverse; + -ms-flex-direction: row-reverse; + flex-direction: row-reverse; +} + +.emotion-14 { + border-bottom: none; + padding: 0 16px; + height: 50px; + font-size: 1rem; +} + +
+ + + + + + + + + + + + + + + + + + + +
+ block + + hash + + type + + result + + time +
+ + + + +
+
+
+ + 11 +
+
+
+
+
+
+
+
+ + 1 day ago + +
+
+
+`; diff --git a/apps/web-stratos/src/screens/home/components/transactions/components/desktop/index.test.tsx b/apps/web-stratos/src/screens/home/components/transactions/components/desktop/index.test.tsx new file mode 100644 index 0000000000..f64d04d9d2 --- /dev/null +++ b/apps/web-stratos/src/screens/home/components/transactions/components/desktop/index.test.tsx @@ -0,0 +1,43 @@ +import renderer from 'react-test-renderer'; +import Desktop from '@/screens/home/components/transactions/components/desktop'; +import MockTheme from '@/tests/mocks/MockTheme'; + +// ================================== +// mocks +// ================================== +jest.mock('@/components/result', () => (props: JSX.IntrinsicElements['div']) => ( +
+)); +jest.mock('@/components/tag', () => (props: JSX.IntrinsicElements['div']) => ( +
+)); + +// ================================== +// unit tests +// ================================== +describe('screen: Home/Transactions/Desktop', () => { + it('matches snapshot', () => { + const component = renderer.create( + + + + ); + const tree = component?.toJSON(); + expect(tree).toMatchSnapshot(); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); +}); diff --git a/apps/web-stratos/src/screens/home/components/transactions/components/desktop/index.tsx b/apps/web-stratos/src/screens/home/components/transactions/components/desktop/index.tsx new file mode 100644 index 0000000000..adf4e9f629 --- /dev/null +++ b/apps/web-stratos/src/screens/home/components/transactions/components/desktop/index.tsx @@ -0,0 +1,123 @@ +import Result from '@/components/result'; +import Tag from '@/components/tag'; +import Timestamp from '@/components/Timestamp'; +import useStyles from '@/screens/home/components/transactions/components/desktop/styles'; +import { columns } from '@/screens/home/components/transactions/components/desktop/utils'; +import type { TransactionType } from '@/screens/home/components/transactions/types'; +import { getMiddleEllipsis } from '@/utils/get_middle_ellipsis'; +import { BLOCK_DETAILS, TRANSACTION_DETAILS } from '@/utils/go_to_page'; +import Table from '@mui/material/Table'; +import TableBody from '@mui/material/TableBody'; +import TableCell from '@mui/material/TableCell'; +import TableHead from '@mui/material/TableHead'; +import TableRow from '@mui/material/TableRow'; +import { AnimatePresence, motion, Variants } from 'framer-motion'; +import useAppTranslation from '@/hooks/useAppTranslation'; +import Link from 'next/link'; +import numeral from 'numeral'; +import { FC } from 'react'; + +type DesktopProps = { + className?: string; + items: TransactionType[]; +}; + +const variants: Variants = { + initial: { + opacity: 0, + height: 50, + display: 'flex', + alignItems: 'center', + overflow: 'hidden', + }, + animate: { + opacity: 1, + height: 50, + display: 'flex', + alignItems: 'center', + overflow: 'hidden', + }, +}; + +const Desktop: FC = ({ className, items }) => { + const { classes, cx } = useStyles(); + const { t } = useAppTranslation('transactions'); + + const formattedData = items.map((x, i) => ({ + key: `${x.hash}-${i}`, + block: ( + + {numeral(x.height).format('0,0')} + + ), + hash: ( + + {getMiddleEllipsis(x.hash, { + beginning: 4, + ending: 4, + })} + + ), + type: ( +
+ + {x.messages > 1 && ` + ${x.messages - 1}`} +
+ ), + result: , + time: , + messages: numeral(x.messages).format('0,0'), + })); + + return ( +
+ + + + {columns.map((column) => ( + + {t(column.key)} + + ))} + + + + + {formattedData.map((row) => ( + // eslint-disable-next-line react/no-array-index-key + + {columns.map((column) => { + const { key, align } = column; + const item = row[key as keyof typeof row]; + return ( + + + {item} + + + ); + })} + + ))} + + +
+
+ ); +}; + +export default Desktop; diff --git a/apps/web-stratos/src/screens/home/components/transactions/components/desktop/styles.ts b/apps/web-stratos/src/screens/home/components/transactions/components/desktop/styles.ts new file mode 100644 index 0000000000..a905ec45ce --- /dev/null +++ b/apps/web-stratos/src/screens/home/components/transactions/components/desktop/styles.ts @@ -0,0 +1,17 @@ +import { makeStyles } from 'tss-react/mui'; + +const useStyles = makeStyles()(() => ({ + root: { + overflow: 'auto', + }, + table: { + '& .MuiTableBody-root': { + '& .MuiTableCell-root': { + whiteSpace: 'nowrap', + height: 'auto', + }, + }, + }, +})); + +export default useStyles; diff --git a/apps/web-stratos/src/screens/home/components/transactions/components/desktop/utils.tsx b/apps/web-stratos/src/screens/home/components/transactions/components/desktop/utils.tsx new file mode 100644 index 0000000000..7be1f6d0db --- /dev/null +++ b/apps/web-stratos/src/screens/home/components/transactions/components/desktop/utils.tsx @@ -0,0 +1,28 @@ +export const columns: { + key: string; + align?: 'left' | 'center' | 'right' | 'justify' | 'inherit'; + width: number; +}[] = [ + { + key: 'block', + width: 20, + }, + { + key: 'hash', + width: 20, + }, + { + key: 'type', + width: 20, + }, + { + key: 'result', + align: 'right', + width: 20, + }, + { + key: 'time', + align: 'right', + width: 20, + }, +]; diff --git a/apps/web-stratos/src/screens/home/components/transactions/components/index.ts b/apps/web-stratos/src/screens/home/components/transactions/components/index.ts new file mode 100644 index 0000000000..e2abe52862 --- /dev/null +++ b/apps/web-stratos/src/screens/home/components/transactions/components/index.ts @@ -0,0 +1,4 @@ +import Desktop from '@/screens/home/components/transactions/components/desktop'; +import Mobile from '@/screens/home/components/transactions/components/mobile'; + +export { Desktop, Mobile }; diff --git a/apps/web-stratos/src/screens/home/components/transactions/components/mobile/__snapshots__/index.test.tsx.snap b/apps/web-stratos/src/screens/home/components/transactions/components/mobile/__snapshots__/index.test.tsx.snap new file mode 100644 index 0000000000..8d7d0767c5 --- /dev/null +++ b/apps/web-stratos/src/screens/home/components/transactions/components/mobile/__snapshots__/index.test.tsx.snap @@ -0,0 +1,47 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`screen: Home/Transactions/Mobile matches snapshot 1`] = ` +
+
+ 2,000 + + } + hash={ + + 76nwV8zz8tLz97S...hQ857 + + } + id="SingleTransactionMobile" + messages="12" + result={ + + } + time={ + + } + type={ +
+ + + 11 +
+ } + /> +
+`; diff --git a/apps/web-stratos/src/screens/home/components/transactions/components/mobile/index.test.tsx b/apps/web-stratos/src/screens/home/components/transactions/components/mobile/index.test.tsx new file mode 100644 index 0000000000..67b12c31b5 --- /dev/null +++ b/apps/web-stratos/src/screens/home/components/transactions/components/mobile/index.test.tsx @@ -0,0 +1,46 @@ +import renderer from 'react-test-renderer'; +import Mobile from '@/screens/home/components/transactions/components/mobile'; +import MockTheme from '@/tests/mocks/MockTheme'; + +// ================================== +// mocks +// ================================== +jest.mock('@/components/single_transaction_mobile', () => (props: JSX.IntrinsicElements['div']) => ( +
+)); +jest.mock('@/components/result', () => (props: JSX.IntrinsicElements['div']) => ( +
+)); +jest.mock('@/components/tag', () => (props: JSX.IntrinsicElements['div']) => ( +
+)); + +// ================================== +// unit tests +// ================================== +describe('screen: Home/Transactions/Mobile', () => { + it('matches snapshot', () => { + const component = renderer.create( + + + + ); + const tree = component?.toJSON(); + expect(tree).toMatchSnapshot(); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); +}); diff --git a/apps/web-stratos/src/screens/home/components/transactions/components/mobile/index.tsx b/apps/web-stratos/src/screens/home/components/transactions/components/mobile/index.tsx new file mode 100644 index 0000000000..ac25574a95 --- /dev/null +++ b/apps/web-stratos/src/screens/home/components/transactions/components/mobile/index.tsx @@ -0,0 +1,57 @@ +import Result from '@/components/result'; +import SingleTransactionMobile from '@/components/single_transaction_mobile'; +import Tag from '@/components/tag'; +import Timestamp from '@/components/Timestamp'; +import type { TransactionType } from '@/screens/home/components/transactions/types'; +import { getMiddleEllipsis } from '@/utils/get_middle_ellipsis'; +import { BLOCK_DETAILS, TRANSACTION_DETAILS } from '@/utils/go_to_page'; +import Divider from '@mui/material/Divider'; +import Link from 'next/link'; +import numeral from 'numeral'; +import { FC, Fragment } from 'react'; + +type MobileProps = { + className?: string; + items: TransactionType[]; +}; + +const Mobile: FC = ({ className, items }) => { + const formattedData = items.map((x, i) => ({ + key: `${x.hash}-${i}`, + block: ( + + {numeral(x.height).format('0,0')} + + ), + hash: ( + + {getMiddleEllipsis(x.hash, { + beginning: 15, + ending: 5, + })} + + ), + type: ( +
+ + {x.messages > 1 && ` + ${x.messages - 1}`} +
+ ), + result: , + time: , + messages: numeral(x.messages).format('0,0'), + })); + + return ( +
+ {formattedData.map((x, i) => ( + + + {i !== formattedData.length - 1 && } + + ))} +
+ ); +}; + +export default Mobile; diff --git a/apps/web-stratos/src/screens/home/components/transactions/hooks.ts b/apps/web-stratos/src/screens/home/components/transactions/hooks.ts new file mode 100644 index 0000000000..b121113c97 --- /dev/null +++ b/apps/web-stratos/src/screens/home/components/transactions/hooks.ts @@ -0,0 +1,53 @@ +import * as R from 'ramda'; +import { useState } from 'react'; +import { + TransactionsListenerSubscription, + useTransactionsListenerSubscription, +} from '@/graphql/types/general_types'; +import type { TransactionsState } from '@/screens/home/components/transactions/types'; +import { convertMsgType } from '@/utils/convert_msg_type'; + +const formatTransactions = (data: TransactionsListenerSubscription) => + data.transactions?.map((x) => { + const msgType = + x.messages?.map((eachMsg: object) => { + const eachMsgType = R.pathOr('none type', ['@type'], eachMsg); + return eachMsgType ?? ''; + }) ?? []; + const convertedMsgType = convertMsgType(msgType); + + return { + height: x.height, + hash: x.hash, + type: convertedMsgType, + success: x.success, + timestamp: x.block.timestamp, + messages: x.messages.length, + }; + }) ?? []; + +export const useTransactions = () => { + const [state, setState] = useState({ + loading: true, + items: [], + }); + + // ================================ + // txs subscription + // ================================ + useTransactionsListenerSubscription({ + onData: (data) => { + setState((prevState) => { + const newState = { + loading: false, + items: data.data.data ? formatTransactions(data.data.data) : [], + }; + return R.equals(prevState, newState) ? prevState : newState; + }); + }, + }); + + return { + state, + }; +}; diff --git a/apps/web-stratos/src/screens/home/components/transactions/index.test.tsx b/apps/web-stratos/src/screens/home/components/transactions/index.test.tsx new file mode 100644 index 0000000000..27510dd93b --- /dev/null +++ b/apps/web-stratos/src/screens/home/components/transactions/index.test.tsx @@ -0,0 +1,86 @@ +import { TransactionsListenerDocument } from '@/graphql/types/general_types'; +import Transactions from '@/screens/home/components/transactions'; +import { mockClient } from '@/tests/mocks/mockApollo'; +import MockTheme from '@/tests/mocks/MockTheme'; +import wait from '@/tests/utils/wait'; +import { ApolloProvider } from '@apollo/client'; +import { MockedProvider } from '@apollo/client/testing'; +import renderer from 'react-test-renderer'; + +// ================================== +// mocks +// ================================== +jest.mock('@/components/box', () => (props: JSX.IntrinsicElements['div']) => ( +
+)); +jest.mock('@/components/no_data', () => (props: JSX.IntrinsicElements['div']) => ( +
+)); + +jest.mock( + '@/screens/home/components/transactions/components/mobile', + () => (props: JSX.IntrinsicElements['div']) =>
+); +jest.mock( + '@/screens/home/components/transactions/components/desktop', + () => (props: JSX.IntrinsicElements['div']) =>
+); +jest.mock('@/components/box', () => (props: JSX.IntrinsicElements['div']) => ( +
+)); + +const mockTxsListenerDocument = jest.fn().mockReturnValue({ + data: { + transactions: [ + { + height: 2000, + block: { + timestamp: '2021-05-28T00:08:33.700487', + }, + hash: '76nwV8zz8tLz97SBRXH6uwHvgHXtqJDLQfF66jZhQ857', + messages: [], + success: true, + logs: [], + }, + ], + }, +}); + +// ================================== +// unit tests +// ================================== +describe('screen: Home/Blocks/Mobile', () => { + it('matches snapshot', async () => { + let component: renderer.ReactTestRenderer | undefined; + + renderer.act(() => { + component = renderer.create( + + + + + + + + ); + }); + + await wait(renderer.act); + const tree = component?.toJSON(); + expect(tree).toMatchSnapshot(); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); +}); diff --git a/apps/web-stratos/src/screens/home/components/transactions/index.tsx b/apps/web-stratos/src/screens/home/components/transactions/index.tsx new file mode 100644 index 0000000000..c8dda7dc74 --- /dev/null +++ b/apps/web-stratos/src/screens/home/components/transactions/index.tsx @@ -0,0 +1,52 @@ +/* eslint-disable no-nested-ternary */ +import Box from '@/components/box'; +import Loading from '@/components/loading'; +import NoData from '@/components/no_data'; +import Desktop from '@/screens/home/components/transactions/components/desktop'; +import Mobile from '@/screens/home/components/transactions/components/mobile'; +import { useTransactions } from '@/screens/home/components/transactions/hooks'; +import useStyles from '@/screens/home/components/transactions/styles'; +import { useDisplayStyles } from '@/styles/useSharedStyles'; +import { TRANSACTIONS } from '@/utils/go_to_page'; +import Divider from '@mui/material/Divider'; +import Typography from '@mui/material/Typography'; +import useAppTranslation from '@/hooks/useAppTranslation'; +import Link from 'next/link'; +import { FC } from 'react'; + +const Transactions: FC = ({ className }) => { + const { t } = useAppTranslation('home'); + const { state } = useTransactions(); + const { classes, cx } = useStyles(); + const display = useDisplayStyles().classes; + return ( + +
+ {t('latestTransactions')} + + {t('seeMore')} + +
+ {state.items.length ? ( + <> + + + + + {t('seeMore')} + + + ) : state.loading ? ( + + ) : ( + + )} +
+ ); +}; + +export default Transactions; diff --git a/apps/web-stratos/src/screens/home/components/transactions/styles.ts b/apps/web-stratos/src/screens/home/components/transactions/styles.ts new file mode 100644 index 0000000000..286a5ec4f2 --- /dev/null +++ b/apps/web-stratos/src/screens/home/components/transactions/styles.ts @@ -0,0 +1,31 @@ +import { makeStyles } from 'tss-react/mui'; + +const useStyles = makeStyles()((theme) => ({ + root: { + display: 'flex', + flexDirection: 'column', + '& a': { + color: theme.palette.custom.fonts.highlight, + }, + '& .button': { + color: theme.palette.custom.fonts.fontTwo, + '&:hover': { + cursor: 'pointer', + }, + }, + }, + label: { + marginBottom: theme.spacing(2), + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + }, + seeMoreFooter: { + paddingTop: theme.spacing(2), + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + }, +})); + +export default useStyles; diff --git a/apps/web-stratos/src/screens/home/components/transactions/types.ts b/apps/web-stratos/src/screens/home/components/transactions/types.ts new file mode 100644 index 0000000000..6f647119f7 --- /dev/null +++ b/apps/web-stratos/src/screens/home/components/transactions/types.ts @@ -0,0 +1,13 @@ +export interface TransactionType { + height: number; + hash: string; + type: string[]; + success: boolean; + timestamp: string; + messages: number; +} + +export interface TransactionsState { + loading: boolean; + items: TransactionType[]; +} diff --git a/apps/web-stratos/src/screens/home/index.test.tsx b/apps/web-stratos/src/screens/home/index.test.tsx new file mode 100644 index 0000000000..d3f0bc9129 --- /dev/null +++ b/apps/web-stratos/src/screens/home/index.test.tsx @@ -0,0 +1,74 @@ +import { ApolloClient, ApolloProvider, from, InMemoryCache } from '@apollo/client'; +import { MockedProvider } from '@apollo/client/testing'; +import renderer from 'react-test-renderer'; +import { LatestBlockTimestampDocument } from '@/graphql/types/general_types'; +import Home from '@/screens/home'; +import MockTheme from '@/tests/mocks/MockTheme'; +import wait from '@/tests/utils/wait'; + +// ================================== +// mocks +// ================================== +jest.mock('@/components/layout', () => (props: JSX.IntrinsicElements['div']) => ( +
+)); + +jest.mock('@/screens/home/components/data_blocks', () => (props: JSX.IntrinsicElements['div']) => ( +
+)); +jest.mock('@/screens/home/components/hero', () => (props: JSX.IntrinsicElements['div']) => ( +
+)); +jest.mock('@/screens/home/components/consensus', () => (props: JSX.IntrinsicElements['div']) => ( +
+)); +jest.mock('@/screens/home/components/tokenomics', () => (props: JSX.IntrinsicElements['div']) => ( +
+)); +jest.mock('@/screens/home/components/blocks', () => (props: JSX.IntrinsicElements['div']) => ( +
+)); +jest.mock('@/screens/home/components/transactions', () => (props: JSX.IntrinsicElements['div']) => ( +
+)); + +const mockBlockTime = jest.fn().mockReturnValue({ + data: { + block: [ + { + timestamp: '2021-06-25T03:23:27.762512', + }, + ], + }, +}); + +// ================================== +// unit tests +// ================================== +describe('screen: Home', () => { + it('matches snapshot', async () => { + let component: renderer.ReactTestRenderer | undefined; + + renderer.act(() => { + component = renderer.create( + + + + + + + + ); + }); + await wait(renderer.act); + + const tree = component?.toJSON(); + expect(tree).toMatchSnapshot(); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); +}); diff --git a/apps/web-stratos/src/screens/home/index.tsx b/apps/web-stratos/src/screens/home/index.tsx new file mode 100644 index 0000000000..559daaa9b9 --- /dev/null +++ b/apps/web-stratos/src/screens/home/index.tsx @@ -0,0 +1,25 @@ +import Layout from '@/components/layout'; +import Blocks from '@/screens/home/components/blocks'; +import Consensus from '@/screens/home/components/consensus'; +import DataBlocks from '@/screens/home/components/data_blocks'; +import Hero from '@/screens/home/components/hero'; +import Tokenomics from '@/screens/home/components/tokenomics'; +import Transactions from '@/screens/home/components/transactions'; +import useStyles from '@/screens/home/styles'; + +const Home = () => { + const { classes } = useStyles(); + + return ( + + + + + + + + + ); +}; + +export default Home; diff --git a/apps/web-stratos/src/screens/home/styles.ts b/apps/web-stratos/src/screens/home/styles.ts new file mode 100644 index 0000000000..ead7aec3b7 --- /dev/null +++ b/apps/web-stratos/src/screens/home/styles.ts @@ -0,0 +1,78 @@ +import { CSSObject } from '@emotion/react'; +import { makeStyles } from 'tss-react/mui'; + +const useStyles = makeStyles()((theme) => ({ + root: { + ...(theme.mixins.layout as CSSObject), + display: 'grid', + gap: theme.spacing(1), + gridTemplateRows: 'auto auto 1fr', + gridTemplateColumns: 'repeat(1, 1fr)', + '& a': { + color: theme.palette.custom.fonts.highlight, + }, + [theme.breakpoints.up('md')]: { + gridTemplateColumns: 'repeat(2, 1fr)', + }, + [theme.breakpoints.up('lg')]: { + gap: theme.spacing(2), + gridTemplateColumns: 'repeat(4, 1fr)', + }, + }, + dataBlocks: { + [theme.breakpoints.up('md')]: { + gridColumn: '1 / 3', + }, + [theme.breakpoints.up('lg')]: { + gridColumn: '1 / 5', + }, + }, + hero: { + [theme.breakpoints.up('md')]: { + gridColumn: '1 / 3', + }, + [theme.breakpoints.up('lg')]: { + gridColumn: '1 / 2', + // height: '400px', // if we can get the change feature + height: '100%', + }, + }, + tokenomics: { + height: '375px', + [theme.breakpoints.up('md')]: { + gridColumn: '1 / 3', + }, + [theme.breakpoints.up('lg')]: { + gridColumn: '2 / 4', + height: '100%', + }, + }, + consensus: { + height: '375px', + [theme.breakpoints.up('md')]: { + gridColumn: '1 / 3', + }, + [theme.breakpoints.up('lg')]: { + gridColumn: '4 / 5', + height: '100%', + }, + }, + blocks: { + [theme.breakpoints.up('md')]: { + gridColumn: '1 / 3', + }, + [theme.breakpoints.up('lg')]: { + gridColumn: '1 / 3', + }, + }, + transactions: { + [theme.breakpoints.up('md')]: { + gridColumn: '1 / 3', + }, + [theme.breakpoints.up('lg')]: { + gridColumn: '3 / 5', + }, + }, +})); + +export default useStyles; diff --git a/package.json b/package.json index e0703c7ad8..ccffedef32 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,8 @@ }, "packageManager": "yarn@3.5.0", "engines": { - "yarn": ">=1.22" + "yarn": ">=1.22", + "node": ">=16.14.2 <17.0.0" }, "lint-staged": { "*.{js,jsx,ts,tsx,mjs,cjs,css,md}": "npx prettier --write" diff --git a/packages/ui/public/locales/en/common.json b/packages/ui/public/locales/en/common.json index 1ec4f8f51b..7708b0a063 100644 --- a/packages/ui/public/locales/en/common.json +++ b/packages/ui/public/locales/en/common.json @@ -31,6 +31,7 @@ "price": "Price", "inflation": "Inflation", "apr": "APR", + "miningReward": "Mining Reward", "success": "Success", "fail": "Fail", "filterBy": "Filter By...", @@ -90,4 +91,4 @@ "connectWalletConnect": "Connect Wallet Connect", "scanWalletConnectQR": "Please scan the QR with a WalletConnect compatible Wallet.", "walletTitle": "Connected wallets" -} +} \ No newline at end of file diff --git a/packages/ui/public/locales/en/home.json b/packages/ui/public/locales/en/home.json index 3b905700d0..6eb8f0bbf3 100644 --- a/packages/ui/public/locales/en/home.json +++ b/packages/ui/public/locales/en/home.json @@ -12,9 +12,15 @@ "bondedPercent": "Bonded\n{{percent}}", "unbondedPercent": "Unbonded\n{{percent}}", "unbondingPercent": "Unbonding\n{{percent}}", + "toBeMinedPercent": "To be mined\n{{percent}}", + "depositPercent": "Deposit\n{{percent}}", + "circulationSupplyPercent": "Circulation supply\n{{percent}}", "bonded": "Bonded", "unbonded": "Unbonded", "unbonding": "Unbonding", + "toBeMined": "To be mined", + "deposit": "Deposit", + "circulationSupply": "Circulating", "others": "others", "latestBlocks": "Latest Blocks", "seeMore": "See More", @@ -32,4 +38,4 @@ "validators": "Validators", "priceHistory": "Price (~48h)", "dataFrom": "Data from" -} +} \ No newline at end of file diff --git a/packages/ui/public/locales/it/common.json b/packages/ui/public/locales/it/common.json index 2cc8d202ba..a6926b89b9 100644 --- a/packages/ui/public/locales/it/common.json +++ b/packages/ui/public/locales/it/common.json @@ -31,6 +31,7 @@ "price": "Prezzo", "inflation": "Inflazione", "apr": "APR", + "miningReward": "Ricompensa Mineraria", "success": "Successo", "fail": "Fallita", "filterBy": "Filtra per...", @@ -90,4 +91,4 @@ "connectWalletConnect": "Connect Wallet Connect", "scanWalletConnectQR": "Please scan the QR with a WalletConnect compatible Wallet.", "walletTitle": "Connected wallets" -} +} \ No newline at end of file diff --git a/packages/ui/public/locales/pl/common.json b/packages/ui/public/locales/pl/common.json index f058d8fe15..c83e5403ab 100644 --- a/packages/ui/public/locales/pl/common.json +++ b/packages/ui/public/locales/pl/common.json @@ -31,6 +31,7 @@ "price": "Cena", "inflation": "Inflacja", "apr": "RRSO", + "miningReward": "Nagroda za wydobycie", "success": "Sukces", "fail": "Porażka", "filterBy": "Filtruj według...", @@ -90,4 +91,4 @@ "connectWalletConnect": "Connect Wallet Connect", "scanWalletConnectQR": "Please scan the QR with a WalletConnect compatible Wallet.", "walletTitle": "Connected wallets" -} +} \ No newline at end of file diff --git a/packages/ui/public/locales/zhs/common.json b/packages/ui/public/locales/zhs/common.json index 184bb3a585..b077d4a0e2 100644 --- a/packages/ui/public/locales/zhs/common.json +++ b/packages/ui/public/locales/zhs/common.json @@ -31,6 +31,7 @@ "price": "价格", "inflation": "通胀", "apr": "年利率", + "miningReward": "挖矿奖励", "success": "成功", "fail": "失败", "filterBy": "筛选...", @@ -90,4 +91,4 @@ "connectWalletConnect": "Connect Wallet Connect", "scanWalletConnectQR": "Please scan the QR with a WalletConnect compatible Wallet.", "walletTitle": "Connected wallets" -} +} \ No newline at end of file diff --git a/packages/ui/public/locales/zht/common.json b/packages/ui/public/locales/zht/common.json index cc6751ad17..3b942087c9 100644 --- a/packages/ui/public/locales/zht/common.json +++ b/packages/ui/public/locales/zht/common.json @@ -29,6 +29,7 @@ "price": "價格", "inflation": "通貨膨脹率", "apr": "年利率", + "miningReward": "挖礦獎勵", "success": "成功", "fail": "失敗", "filterBy": "過濾...", @@ -88,4 +89,4 @@ "connectWalletConnect": "Connect Wallet Connect", "scanWalletConnectQR": "Please scan the QR with a WalletConnect compatible Wallet.", "walletTitle": "Connected wallets" -} +} \ No newline at end of file diff --git a/packages/ui/src/components/nav/components/mobile/__snapshots__/index.test.tsx.snap b/packages/ui/src/components/nav/components/mobile/__snapshots__/index.test.tsx.snap index 6f38891893..438aab22c5 100644 --- a/packages/ui/src/components/nav/components/mobile/__snapshots__/index.test.tsx.snap +++ b/packages/ui/src/components/nav/components/mobile/__snapshots__/index.test.tsx.snap @@ -338,7 +338,7 @@ exports[`screen: Nav/Mobile it renders 1`] = ` loading="lazy" onError={[Function]} onLoad={[Function]} - src="https://raw.githubusercontent.com/forbole/big-dipper-networks/main/logos/desmos.svg?sanitize=true" + src="https://raw.githubusercontent.com/stratosnet/big-dipper-networks/main/logos/desmos.svg?sanitize=true" style={ { "color": "transparent", diff --git a/packages/ui/src/graphql/useApollo/index.ts b/packages/ui/src/graphql/useApollo/index.ts index 237bbc0426..0f8e4374d2 100644 --- a/packages/ui/src/graphql/useApollo/index.ts +++ b/packages/ui/src/graphql/useApollo/index.ts @@ -102,7 +102,7 @@ export function profileApi() { return 'https://gql.mainnet.desmos.network/v1/graphql'; } -export const BIG_DIPPER_NETWORKS = 'https://raw.githubusercontent.com/forbole/big-dipper-networks/main/'; +export const BIG_DIPPER_NETWORKS = 'https://raw.githubusercontent.com/stratosnet/big-dipper-networks/main/'; /** * It creates a new Apollo Client, and sets the default options for it diff --git a/packages/ui/src/hooks/useBigDipperNetworks/mocks.ts b/packages/ui/src/hooks/useBigDipperNetworks/mocks.ts index 329a06236d..b354a7e08f 100644 --- a/packages/ui/src/hooks/useBigDipperNetworks/mocks.ts +++ b/packages/ui/src/hooks/useBigDipperNetworks/mocks.ts @@ -6,9 +6,9 @@ const mockQueryResult = jest.fn().mockReturnValue({ networks: [ { name: 'Desmos', - logo: 'https://raw.githubusercontent.com/forbole/big-dipper-networks/main/logos/desmos.svg?sanitize=true', + logo: 'https://raw.githubusercontent.com/stratosnet/big-dipper-networks/main/logos/desmos.svg?sanitize=true', cover: - 'https://raw.githubusercontent.com/forbole/big-dipper-networks/main/covers/desmos.png?sanitize=true', + 'https://raw.githubusercontent.com/stratosnet/big-dipper-networks/main/covers/desmos.png?sanitize=true', links: [ { name: 'Testnet', diff --git a/packages/ui/src/recoil/market/hooks.ts b/packages/ui/src/recoil/market/hooks.ts index e8e404eaf9..bb045a79c8 100644 --- a/packages/ui/src/recoil/market/hooks.ts +++ b/packages/ui/src/recoil/market/hooks.ts @@ -16,7 +16,7 @@ const { primaryTokenUnit, tokenUnits } = chainConfig(); export function useMarketRecoil() { const [market, setMarket] = useRecoilState(writeMarket) as [ AtomState, - SetterOrUpdater + SetterOrUpdater, ]; useMarketDataQuery({ diff --git a/packages/ui/src/recoil/market/types.ts b/packages/ui/src/recoil/market/types.ts index 828e211b55..728b060285 100644 --- a/packages/ui/src/recoil/market/types.ts +++ b/packages/ui/src/recoil/market/types.ts @@ -5,4 +5,5 @@ export interface AtomState { inflation: number; communityPool: TokenUnit; apr: number; + miningReward: number | null; } diff --git a/turbo.json b/turbo.json index 786d29928c..ef62c4b7d7 100644 --- a/turbo.json +++ b/turbo.json @@ -7,6 +7,7 @@ "env": [ "NEXT_PUBLIC_BANNERS_JSON", "NEXT_PUBLIC_COINZILLA_ZONE", + "NEXT_PUBLIC_REST_CHAIN_ENDPOINT", "NEXT_PUBLIC_GRAPHQL_URL", "NEXT_PUBLIC_GRAPHQL_WS", "NEXT_PUBLIC_MATOMO_SITE_ID", @@ -57,6 +58,7 @@ "NEXT_PUBLIC_SENTRY_DSN", "NEXT_PUBLIC_RELEASE", "NEXT_PUBLIC_WC_BRIDGE_URL", + "NEXT_PUBLIC_REST_CHAIN_ENDPOINT", "NODE_ENV", "npm_package_version", "PORT", diff --git a/yarn.lock b/yarn.lock index cd5f8149fa..4c8434d12f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2281,7 +2281,95 @@ __metadata: languageName: node linkType: hard -"@ethersproject/bytes@npm:^5.7.0": +"@ethersproject/abi@npm:5.7.0, @ethersproject/abi@npm:^5.7.0": + version: 5.7.0 + resolution: "@ethersproject/abi@npm:5.7.0" + dependencies: + "@ethersproject/address": ^5.7.0 + "@ethersproject/bignumber": ^5.7.0 + "@ethersproject/bytes": ^5.7.0 + "@ethersproject/constants": ^5.7.0 + "@ethersproject/hash": ^5.7.0 + "@ethersproject/keccak256": ^5.7.0 + "@ethersproject/logger": ^5.7.0 + "@ethersproject/properties": ^5.7.0 + "@ethersproject/strings": ^5.7.0 + checksum: bc6962bb6cb854e4d2a4d65b2c49c716477675b131b1363312234bdbb7e19badb7d9ce66f4ca2a70ae2ea84f7123dbc4e300a1bfe5d58864a7eafabc1466627e + languageName: node + linkType: hard + +"@ethersproject/abstract-provider@npm:5.7.0, @ethersproject/abstract-provider@npm:^5.7.0": + version: 5.7.0 + resolution: "@ethersproject/abstract-provider@npm:5.7.0" + dependencies: + "@ethersproject/bignumber": ^5.7.0 + "@ethersproject/bytes": ^5.7.0 + "@ethersproject/logger": ^5.7.0 + "@ethersproject/networks": ^5.7.0 + "@ethersproject/properties": ^5.7.0 + "@ethersproject/transactions": ^5.7.0 + "@ethersproject/web": ^5.7.0 + checksum: 74cf4696245cf03bb7cc5b6cbf7b4b89dd9a79a1c4688126d214153a938126d4972d42c93182198653ce1de35f2a2cad68be40337d4774b3698a39b28f0228a8 + languageName: node + linkType: hard + +"@ethersproject/abstract-signer@npm:5.7.0, @ethersproject/abstract-signer@npm:^5.7.0": + version: 5.7.0 + resolution: "@ethersproject/abstract-signer@npm:5.7.0" + dependencies: + "@ethersproject/abstract-provider": ^5.7.0 + "@ethersproject/bignumber": ^5.7.0 + "@ethersproject/bytes": ^5.7.0 + "@ethersproject/logger": ^5.7.0 + "@ethersproject/properties": ^5.7.0 + checksum: a823dac9cfb761e009851050ebebd5b229d1b1cc4a75b125c2da130ff37e8218208f7f9d1386f77407705b889b23d4a230ad67185f8872f083143e0073cbfbe3 + languageName: node + linkType: hard + +"@ethersproject/address@npm:5.7.0, @ethersproject/address@npm:^5.7.0": + version: 5.7.0 + resolution: "@ethersproject/address@npm:5.7.0" + dependencies: + "@ethersproject/bignumber": ^5.7.0 + "@ethersproject/bytes": ^5.7.0 + "@ethersproject/keccak256": ^5.7.0 + "@ethersproject/logger": ^5.7.0 + "@ethersproject/rlp": ^5.7.0 + checksum: 64ea5ebea9cc0e845c413e6cb1e54e157dd9fc0dffb98e239d3a3efc8177f2ff798cd4e3206cf3660ee8faeb7bef1a47dc0ebef0d7b132c32e61e550c7d4c843 + languageName: node + linkType: hard + +"@ethersproject/base64@npm:5.7.0, @ethersproject/base64@npm:^5.7.0": + version: 5.7.0 + resolution: "@ethersproject/base64@npm:5.7.0" + dependencies: + "@ethersproject/bytes": ^5.7.0 + checksum: 7dd5d734d623582f08f665434f53685041a3d3b334a0e96c0c8afa8bbcaab934d50e5b6b980e826a8fde8d353e0b18f11e61faf17468177274b8e7c69cd9742b + languageName: node + linkType: hard + +"@ethersproject/basex@npm:5.7.0, @ethersproject/basex@npm:^5.7.0": + version: 5.7.0 + resolution: "@ethersproject/basex@npm:5.7.0" + dependencies: + "@ethersproject/bytes": ^5.7.0 + "@ethersproject/properties": ^5.7.0 + checksum: 326087b7e1f3787b5fe6cd1cf2b4b5abfafbc355a45e88e22e5e9d6c845b613ffc5301d629b28d5c4d5e2bfe9ec424e6782c804956dff79be05f0098cb5817de + languageName: node + linkType: hard + +"@ethersproject/bignumber@npm:5.7.0, @ethersproject/bignumber@npm:^5.7.0": + version: 5.7.0 + resolution: "@ethersproject/bignumber@npm:5.7.0" + dependencies: + "@ethersproject/bytes": ^5.7.0 + "@ethersproject/logger": ^5.7.0 + bn.js: ^5.2.1 + checksum: 8c9a134b76f3feb4ec26a5a27379efb4e156b8fb2de0678a67788a91c7f4e30abe9d948638458e4b20f2e42380da0adacc7c9389d05fce070692edc6ae9b4904 + languageName: node + linkType: hard + +"@ethersproject/bytes@npm:5.7.0, @ethersproject/bytes@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/bytes@npm:5.7.0" dependencies: @@ -2290,7 +2378,92 @@ __metadata: languageName: node linkType: hard -"@ethersproject/keccak256@npm:^5.5.0": +"@ethersproject/constants@npm:5.7.0, @ethersproject/constants@npm:^5.7.0": + version: 5.7.0 + resolution: "@ethersproject/constants@npm:5.7.0" + dependencies: + "@ethersproject/bignumber": ^5.7.0 + checksum: 6d4b1355747cce837b3e76ec3bde70e4732736f23b04f196f706ebfa5d4d9c2be50904a390d4d40ce77803b98d03d16a9b6898418e04ba63491933ce08c4ba8a + languageName: node + linkType: hard + +"@ethersproject/contracts@npm:5.7.0": + version: 5.7.0 + resolution: "@ethersproject/contracts@npm:5.7.0" + dependencies: + "@ethersproject/abi": ^5.7.0 + "@ethersproject/abstract-provider": ^5.7.0 + "@ethersproject/abstract-signer": ^5.7.0 + "@ethersproject/address": ^5.7.0 + "@ethersproject/bignumber": ^5.7.0 + "@ethersproject/bytes": ^5.7.0 + "@ethersproject/constants": ^5.7.0 + "@ethersproject/logger": ^5.7.0 + "@ethersproject/properties": ^5.7.0 + "@ethersproject/transactions": ^5.7.0 + checksum: 6ccf1121cba01b31e02f8c507cb971ab6bfed85706484a9ec09878ef1594a62215f43c4fdef8f4a4875b99c4a800bc95e3be69b1803f8ce479e07634b5a740c0 + languageName: node + linkType: hard + +"@ethersproject/hash@npm:5.7.0, @ethersproject/hash@npm:^5.7.0": + version: 5.7.0 + resolution: "@ethersproject/hash@npm:5.7.0" + dependencies: + "@ethersproject/abstract-signer": ^5.7.0 + "@ethersproject/address": ^5.7.0 + "@ethersproject/base64": ^5.7.0 + "@ethersproject/bignumber": ^5.7.0 + "@ethersproject/bytes": ^5.7.0 + "@ethersproject/keccak256": ^5.7.0 + "@ethersproject/logger": ^5.7.0 + "@ethersproject/properties": ^5.7.0 + "@ethersproject/strings": ^5.7.0 + checksum: 6e9fa8d14eb08171cd32f17f98cc108ec2aeca74a427655f0d689c550fee0b22a83b3b400fad7fb3f41cf14d4111f87f170aa7905bcbcd1173a55f21b06262ef + languageName: node + linkType: hard + +"@ethersproject/hdnode@npm:5.7.0, @ethersproject/hdnode@npm:^5.7.0": + version: 5.7.0 + resolution: "@ethersproject/hdnode@npm:5.7.0" + dependencies: + "@ethersproject/abstract-signer": ^5.7.0 + "@ethersproject/basex": ^5.7.0 + "@ethersproject/bignumber": ^5.7.0 + "@ethersproject/bytes": ^5.7.0 + "@ethersproject/logger": ^5.7.0 + "@ethersproject/pbkdf2": ^5.7.0 + "@ethersproject/properties": ^5.7.0 + "@ethersproject/sha2": ^5.7.0 + "@ethersproject/signing-key": ^5.7.0 + "@ethersproject/strings": ^5.7.0 + "@ethersproject/transactions": ^5.7.0 + "@ethersproject/wordlists": ^5.7.0 + checksum: bfe5ca2d89a42de73655f853170ef4766b933c5f481cddad709b3aca18823275b096e572f92d1602a052f80b426edde44ad6b9d028799775a7dad4a5bbed2133 + languageName: node + linkType: hard + +"@ethersproject/json-wallets@npm:5.7.0, @ethersproject/json-wallets@npm:^5.7.0": + version: 5.7.0 + resolution: "@ethersproject/json-wallets@npm:5.7.0" + dependencies: + "@ethersproject/abstract-signer": ^5.7.0 + "@ethersproject/address": ^5.7.0 + "@ethersproject/bytes": ^5.7.0 + "@ethersproject/hdnode": ^5.7.0 + "@ethersproject/keccak256": ^5.7.0 + "@ethersproject/logger": ^5.7.0 + "@ethersproject/pbkdf2": ^5.7.0 + "@ethersproject/properties": ^5.7.0 + "@ethersproject/random": ^5.7.0 + "@ethersproject/strings": ^5.7.0 + "@ethersproject/transactions": ^5.7.0 + aes-js: 3.0.0 + scrypt-js: 3.0.1 + checksum: f583458d22db62efaaf94d38dd243482776a45bf90f9f3882fbad5aa0b8fd288b41eb7c1ff8ec0b99c9b751088e43d6173530db64dd33c59f9d8daa8d7ad5aa2 + languageName: node + linkType: hard + +"@ethersproject/keccak256@npm:5.7.0, @ethersproject/keccak256@npm:^5.5.0, @ethersproject/keccak256@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/keccak256@npm:5.7.0" dependencies: @@ -2300,13 +2473,216 @@ __metadata: languageName: node linkType: hard -"@ethersproject/logger@npm:^5.7.0": +"@ethersproject/logger@npm:5.7.0, @ethersproject/logger@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/logger@npm:5.7.0" checksum: 075ab2f605f1fd0813f2e39c3308f77b44a67732b36e712d9bc085f22a84aac4da4f71b39bee50fe78da3e1c812673fadc41180c9970fe5e486e91ea17befe0d languageName: node linkType: hard +"@ethersproject/networks@npm:5.7.1, @ethersproject/networks@npm:^5.7.0": + version: 5.7.1 + resolution: "@ethersproject/networks@npm:5.7.1" + dependencies: + "@ethersproject/logger": ^5.7.0 + checksum: 0339f312304c17d9a0adce550edb825d4d2c8c9468c1634c44172c67a9ed256f594da62c4cda5c3837a0f28b7fabc03aca9b492f68ff1fdad337ee861b27bd5d + languageName: node + linkType: hard + +"@ethersproject/pbkdf2@npm:5.7.0, @ethersproject/pbkdf2@npm:^5.7.0": + version: 5.7.0 + resolution: "@ethersproject/pbkdf2@npm:5.7.0" + dependencies: + "@ethersproject/bytes": ^5.7.0 + "@ethersproject/sha2": ^5.7.0 + checksum: b895adb9e35a8a127e794f7aadc31a2424ef355a70e51cde10d457e3e888bb8102373199a540cf61f2d6b9a32e47358f9c65b47d559f42bf8e596b5fd67901e9 + languageName: node + linkType: hard + +"@ethersproject/properties@npm:5.7.0, @ethersproject/properties@npm:^5.7.0": + version: 5.7.0 + resolution: "@ethersproject/properties@npm:5.7.0" + dependencies: + "@ethersproject/logger": ^5.7.0 + checksum: 6ab0ccf0c3aadc9221e0cdc5306ce6cd0df7f89f77d77bccdd1277182c9ead0202cd7521329ba3acde130820bf8af299e17cf567d0d497c736ee918207bbf59f + languageName: node + linkType: hard + +"@ethersproject/providers@npm:5.7.2": + version: 5.7.2 + resolution: "@ethersproject/providers@npm:5.7.2" + dependencies: + "@ethersproject/abstract-provider": ^5.7.0 + "@ethersproject/abstract-signer": ^5.7.0 + "@ethersproject/address": ^5.7.0 + "@ethersproject/base64": ^5.7.0 + "@ethersproject/basex": ^5.7.0 + "@ethersproject/bignumber": ^5.7.0 + "@ethersproject/bytes": ^5.7.0 + "@ethersproject/constants": ^5.7.0 + "@ethersproject/hash": ^5.7.0 + "@ethersproject/logger": ^5.7.0 + "@ethersproject/networks": ^5.7.0 + "@ethersproject/properties": ^5.7.0 + "@ethersproject/random": ^5.7.0 + "@ethersproject/rlp": ^5.7.0 + "@ethersproject/sha2": ^5.7.0 + "@ethersproject/strings": ^5.7.0 + "@ethersproject/transactions": ^5.7.0 + "@ethersproject/web": ^5.7.0 + bech32: 1.1.4 + ws: 7.4.6 + checksum: 1754c731a5ca6782ae9677f4a9cd8b6246c4ef21a966c9a01b133750f3c578431ec43ec254e699969c4a0f87e84463ded50f96b415600aabd37d2056aee58c19 + languageName: node + linkType: hard + +"@ethersproject/random@npm:5.7.0, @ethersproject/random@npm:^5.7.0": + version: 5.7.0 + resolution: "@ethersproject/random@npm:5.7.0" + dependencies: + "@ethersproject/bytes": ^5.7.0 + "@ethersproject/logger": ^5.7.0 + checksum: 017829c91cff6c76470852855108115b0b52c611b6be817ed1948d56ba42d6677803ec2012aa5ae298a7660024156a64c11fcf544e235e239ab3f89f0fff7345 + languageName: node + linkType: hard + +"@ethersproject/rlp@npm:5.7.0, @ethersproject/rlp@npm:^5.7.0": + version: 5.7.0 + resolution: "@ethersproject/rlp@npm:5.7.0" + dependencies: + "@ethersproject/bytes": ^5.7.0 + "@ethersproject/logger": ^5.7.0 + checksum: bce165b0f7e68e4d091c9d3cf47b247cac33252df77a095ca4281d32d5eeaaa3695d9bc06b2b057c5015353a68df89f13a4a54a72e888e4beeabbe56b15dda6e + languageName: node + linkType: hard + +"@ethersproject/sha2@npm:5.7.0, @ethersproject/sha2@npm:^5.7.0": + version: 5.7.0 + resolution: "@ethersproject/sha2@npm:5.7.0" + dependencies: + "@ethersproject/bytes": ^5.7.0 + "@ethersproject/logger": ^5.7.0 + hash.js: 1.1.7 + checksum: 09321057c022effbff4cc2d9b9558228690b5dd916329d75c4b1ffe32ba3d24b480a367a7cc92d0f0c0b1c896814d03351ae4630e2f1f7160be2bcfbde435dbc + languageName: node + linkType: hard + +"@ethersproject/signing-key@npm:5.7.0, @ethersproject/signing-key@npm:^5.7.0": + version: 5.7.0 + resolution: "@ethersproject/signing-key@npm:5.7.0" + dependencies: + "@ethersproject/bytes": ^5.7.0 + "@ethersproject/logger": ^5.7.0 + "@ethersproject/properties": ^5.7.0 + bn.js: ^5.2.1 + elliptic: 6.5.4 + hash.js: 1.1.7 + checksum: 8f8de09b0aac709683bbb49339bc0a4cd2f95598f3546436c65d6f3c3a847ffa98e06d35e9ed2b17d8030bd2f02db9b7bd2e11c5cf8a71aad4537487ab4cf03a + languageName: node + linkType: hard + +"@ethersproject/solidity@npm:5.7.0": + version: 5.7.0 + resolution: "@ethersproject/solidity@npm:5.7.0" + dependencies: + "@ethersproject/bignumber": ^5.7.0 + "@ethersproject/bytes": ^5.7.0 + "@ethersproject/keccak256": ^5.7.0 + "@ethersproject/logger": ^5.7.0 + "@ethersproject/sha2": ^5.7.0 + "@ethersproject/strings": ^5.7.0 + checksum: 9a02f37f801c96068c3e7721f83719d060175bc4e80439fe060e92bd7acfcb6ac1330c7e71c49f4c2535ca1308f2acdcb01e00133129aac00581724c2d6293f3 + languageName: node + linkType: hard + +"@ethersproject/strings@npm:5.7.0, @ethersproject/strings@npm:^5.7.0": + version: 5.7.0 + resolution: "@ethersproject/strings@npm:5.7.0" + dependencies: + "@ethersproject/bytes": ^5.7.0 + "@ethersproject/constants": ^5.7.0 + "@ethersproject/logger": ^5.7.0 + checksum: 5ff78693ae3fdf3cf23e1f6dc047a61e44c8197d2408c42719fef8cb7b7b3613a4eec88ac0ed1f9f5558c74fe0de7ae3195a29ca91a239c74b9f444d8e8b50df + languageName: node + linkType: hard + +"@ethersproject/transactions@npm:5.7.0, @ethersproject/transactions@npm:^5.7.0": + version: 5.7.0 + resolution: "@ethersproject/transactions@npm:5.7.0" + dependencies: + "@ethersproject/address": ^5.7.0 + "@ethersproject/bignumber": ^5.7.0 + "@ethersproject/bytes": ^5.7.0 + "@ethersproject/constants": ^5.7.0 + "@ethersproject/keccak256": ^5.7.0 + "@ethersproject/logger": ^5.7.0 + "@ethersproject/properties": ^5.7.0 + "@ethersproject/rlp": ^5.7.0 + "@ethersproject/signing-key": ^5.7.0 + checksum: a31b71996d2b283f68486241bff0d3ea3f1ba0e8f1322a8fffc239ccc4f4a7eb2ea9994b8fd2f093283fd75f87bae68171e01b6265261f821369aca319884a79 + languageName: node + linkType: hard + +"@ethersproject/units@npm:5.7.0": + version: 5.7.0 + resolution: "@ethersproject/units@npm:5.7.0" + dependencies: + "@ethersproject/bignumber": ^5.7.0 + "@ethersproject/constants": ^5.7.0 + "@ethersproject/logger": ^5.7.0 + checksum: 304714f848cd32e57df31bf545f7ad35c2a72adae957198b28cbc62166daa929322a07bff6e9c9ac4577ab6aa0de0546b065ed1b2d20b19e25748b7d475cb0fc + languageName: node + linkType: hard + +"@ethersproject/wallet@npm:5.7.0": + version: 5.7.0 + resolution: "@ethersproject/wallet@npm:5.7.0" + dependencies: + "@ethersproject/abstract-provider": ^5.7.0 + "@ethersproject/abstract-signer": ^5.7.0 + "@ethersproject/address": ^5.7.0 + "@ethersproject/bignumber": ^5.7.0 + "@ethersproject/bytes": ^5.7.0 + "@ethersproject/hash": ^5.7.0 + "@ethersproject/hdnode": ^5.7.0 + "@ethersproject/json-wallets": ^5.7.0 + "@ethersproject/keccak256": ^5.7.0 + "@ethersproject/logger": ^5.7.0 + "@ethersproject/properties": ^5.7.0 + "@ethersproject/random": ^5.7.0 + "@ethersproject/signing-key": ^5.7.0 + "@ethersproject/transactions": ^5.7.0 + "@ethersproject/wordlists": ^5.7.0 + checksum: a4009bf7331eddab38e3015b5e9101ef92de7f705b00a6196b997db0e5635b6d83561674d46c90c6f77b87c0500fe4a6b0183ba13749efc22db59c99deb82fbd + languageName: node + linkType: hard + +"@ethersproject/web@npm:5.7.1, @ethersproject/web@npm:^5.7.0": + version: 5.7.1 + resolution: "@ethersproject/web@npm:5.7.1" + dependencies: + "@ethersproject/base64": ^5.7.0 + "@ethersproject/bytes": ^5.7.0 + "@ethersproject/logger": ^5.7.0 + "@ethersproject/properties": ^5.7.0 + "@ethersproject/strings": ^5.7.0 + checksum: 7028c47103f82fd2e2c197ce0eecfacaa9180ffeec7de7845b1f4f9b19d84081b7a48227aaddde05a4aaa526af574a9a0ce01cc0fc75e3e371f84b38b5b16b2b + languageName: node + linkType: hard + +"@ethersproject/wordlists@npm:5.7.0, @ethersproject/wordlists@npm:^5.7.0": + version: 5.7.0 + resolution: "@ethersproject/wordlists@npm:5.7.0" + dependencies: + "@ethersproject/bytes": ^5.7.0 + "@ethersproject/hash": ^5.7.0 + "@ethersproject/logger": ^5.7.0 + "@ethersproject/properties": ^5.7.0 + "@ethersproject/strings": ^5.7.0 + checksum: 30eb6eb0731f9ef5faa44bf9c0c6e950bcaaef61e4d2d9ce0ae6d341f4e2d6d1f4ab4f8880bfce03b7aac4b862fb740e1421170cfbf8e2aafc359277d49e6e97 + languageName: node + linkType: hard + "@gar/promisify@npm:^1.1.3": version: 1.1.3 resolution: "@gar/promisify@npm:1.1.3" @@ -4425,6 +4801,31 @@ __metadata: languageName: node linkType: hard +"@tanstack/query-core@npm:5.0.0": + version: 5.0.0 + resolution: "@tanstack/query-core@npm:5.0.0" + checksum: 19ef7a26d114b03f5899d209ec88c815fc4887c3b3682aca2ec7765b3430bcd085ddccd8434a702affe9ce22c2f3fd5575b823d01ea1f6497c16bb05ec83d668 + languageName: node + linkType: hard + +"@tanstack/react-query@npm:^5.0.0": + version: 5.0.0 + resolution: "@tanstack/react-query@npm:5.0.0" + dependencies: + "@tanstack/query-core": 5.0.0 + peerDependencies: + react: ^18.0.0 + react-dom: ^18.0.0 + react-native: "*" + peerDependenciesMeta: + react-dom: + optional: true + react-native: + optional: true + checksum: 266cdf25aa7a35f44f0f00628b42f815f945e5cbbe2ff0e7d1d40f3800aca430ef1e43c7718bb8cd8ae039f1a483517275f4aaa41932c2d7cf6da374dda4c8b7 + languageName: node + linkType: hard + "@testing-library/dom@npm:^9.0.0": version: 9.2.0 resolution: "@testing-library/dom@npm:9.2.0" @@ -5634,6 +6035,13 @@ __metadata: languageName: node linkType: hard +"aes-js@npm:3.0.0": + version: 3.0.0 + resolution: "aes-js@npm:3.0.0" + checksum: 251e26d533cd1a915b44896b17d5ed68c24a02484cfdd2e74ec700a309267db96651ea4eb657bf20aac32a3baa61f6e34edf8e2fec2de440a655da9942d334b8 + languageName: node + linkType: hard + "aes-js@npm:^3.1.2": version: 3.1.2 resolution: "aes-js@npm:3.1.2" @@ -5991,6 +6399,17 @@ __metadata: languageName: node linkType: hard +"axios@npm:^1.5.1": + version: 1.5.1 + resolution: "axios@npm:1.5.1" + dependencies: + follow-redirects: ^1.15.0 + form-data: ^4.0.0 + proxy-from-env: ^1.1.0 + checksum: 4444f06601f4ede154183767863d2b8e472b4a6bfc5253597ed6d21899887e1fd0ee2b3de792ac4f8459fe2e359d2aa07c216e45fd8b9e4e0688a6ebf48a5a8d + languageName: node + linkType: hard + "axobject-query@npm:^3.1.1": version: 3.1.1 resolution: "axobject-query@npm:3.1.1" @@ -6197,7 +6616,7 @@ __metadata: languageName: node linkType: hard -"bech32@npm:^1.1.4": +"bech32@npm:1.1.4, bech32@npm:^1.1.4": version: 1.1.4 resolution: "bech32@npm:1.1.4" checksum: 0e98db619191548390d6f09ff68b0253ba7ae6a55db93dfdbb070ba234c1fd3308c0606fbcc95fad50437227b10011e2698b89f0181f6e7f845c499bd14d0f4b @@ -6316,7 +6735,7 @@ __metadata: languageName: node linkType: hard -"bn.js@npm:^5.2.0": +"bn.js@npm:^5.2.0, bn.js@npm:^5.2.1": version: 5.2.1 resolution: "bn.js@npm:5.2.1" checksum: 3dd8c8d38055fedfa95c1d5fc3c99f8dd547b36287b37768db0abab3c239711f88ff58d18d155dd8ad902b0b0cee973747b7ae20ea12a09473272b0201c9edd3 @@ -7893,7 +8312,7 @@ __metadata: languageName: node linkType: hard -"elliptic@npm:^6.4.0, elliptic@npm:^6.5.3, elliptic@npm:^6.5.4": +"elliptic@npm:6.5.4, elliptic@npm:^6.4.0, elliptic@npm:^6.5.3, elliptic@npm:^6.5.4": version: 6.5.4 resolution: "elliptic@npm:6.5.4" dependencies: @@ -8495,6 +8914,44 @@ __metadata: languageName: node linkType: hard +"ethers@npm:5.7.2": + version: 5.7.2 + resolution: "ethers@npm:5.7.2" + dependencies: + "@ethersproject/abi": 5.7.0 + "@ethersproject/abstract-provider": 5.7.0 + "@ethersproject/abstract-signer": 5.7.0 + "@ethersproject/address": 5.7.0 + "@ethersproject/base64": 5.7.0 + "@ethersproject/basex": 5.7.0 + "@ethersproject/bignumber": 5.7.0 + "@ethersproject/bytes": 5.7.0 + "@ethersproject/constants": 5.7.0 + "@ethersproject/contracts": 5.7.0 + "@ethersproject/hash": 5.7.0 + "@ethersproject/hdnode": 5.7.0 + "@ethersproject/json-wallets": 5.7.0 + "@ethersproject/keccak256": 5.7.0 + "@ethersproject/logger": 5.7.0 + "@ethersproject/networks": 5.7.1 + "@ethersproject/pbkdf2": 5.7.0 + "@ethersproject/properties": 5.7.0 + "@ethersproject/providers": 5.7.2 + "@ethersproject/random": 5.7.0 + "@ethersproject/rlp": 5.7.0 + "@ethersproject/sha2": 5.7.0 + "@ethersproject/signing-key": 5.7.0 + "@ethersproject/solidity": 5.7.0 + "@ethersproject/strings": 5.7.0 + "@ethersproject/transactions": 5.7.0 + "@ethersproject/units": 5.7.0 + "@ethersproject/wallet": 5.7.0 + "@ethersproject/web": 5.7.1 + "@ethersproject/wordlists": 5.7.0 + checksum: b7c08cf3e257185a7946117dbbf764433b7ba0e77c27298dec6088b3bc871aff711462b0621930c56880ff0a7ceb8b1d3a361ffa259f93377b48e34107f62553 + languageName: node + linkType: hard + "event-target-shim@npm:^5.0.0": version: 5.0.1 resolution: "event-target-shim@npm:5.0.1" @@ -8813,6 +9270,16 @@ __metadata: languageName: node linkType: hard +"follow-redirects@npm:^1.15.0": + version: 1.15.3 + resolution: "follow-redirects@npm:1.15.3" + peerDependenciesMeta: + debug: + optional: true + checksum: 584da22ec5420c837bd096559ebfb8fe69d82512d5585004e36a3b4a6ef6d5905780e0c74508c7b72f907d1fa2b7bd339e613859e9c304d0dc96af2027fd0231 + languageName: node + linkType: hard + "for-each@npm:^0.3.3": version: 0.3.3 resolution: "for-each@npm:0.3.3" @@ -9391,7 +9858,7 @@ __metadata: languageName: node linkType: hard -"hash.js@npm:^1.0.0, hash.js@npm:^1.0.3, hash.js@npm:^1.1.7": +"hash.js@npm:1.1.7, hash.js@npm:^1.0.0, hash.js@npm:^1.0.3, hash.js@npm:^1.1.7": version: 1.1.7 resolution: "hash.js@npm:1.1.7" dependencies: @@ -13695,6 +14162,13 @@ __metadata: languageName: node linkType: hard +"scrypt-js@npm:3.0.1": + version: 3.0.1 + resolution: "scrypt-js@npm:3.0.1" + checksum: b7c7d1a68d6ca946f2fbb0778e0c4ec63c65501b54023b2af7d7e9f48fdb6c6580d6f7675cd53bda5944c5ebc057560d5a6365079752546865defb3b79dea454 + languageName: node + linkType: hard + "scuid@npm:^1.1.0": version: 1.1.0 resolution: "scuid@npm:1.1.0" @@ -15493,6 +15967,7 @@ __metadata: "@next/eslint-plugin-next": ^13.4.1 "@socialgouv/matomo-next": ^1.6.1 "@svgr/webpack": ^7.0.0 + "@tanstack/react-query": ^5.0.0 "@testing-library/jest-dom": ^5.16.5 "@testing-library/react": ^14.0.0 "@types/big.js": ^6.1.6 @@ -15517,6 +15992,7 @@ __metadata: "@walletconnect/encoding": ^1.0.2 "@yarnpkg/pnpify": ^4.0.0-rc.43 apollo-link-rest: ^0.9.0 + axios: ^1.5.1 bech32: ^2.0.0 big.js: ^6.2.1 color: ^4.2.3 @@ -15537,6 +16013,7 @@ __metadata: eslint-plugin-react-hooks: ^4.6.0 eslint-plugin-turbo: ^1.9.3 esprima: ^4.0.1 + ethers: 5.7.2 framer-motion: ^10.12.8 graphql: ^16.6.0 graphql-tag: ^2.12.6 @@ -15822,6 +16299,21 @@ __metadata: languageName: node linkType: hard +"ws@npm:7.4.6": + version: 7.4.6 + resolution: "ws@npm:7.4.6" + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + checksum: 3a990b32ed08c72070d5e8913e14dfcd831919205be52a3ff0b4cdd998c8d554f167c9df3841605cde8b11d607768cacab3e823c58c96a5c08c987e093eb767a + languageName: node + linkType: hard + "ws@npm:7.5.3": version: 7.5.3 resolution: "ws@npm:7.5.3" From c561259483e3689d93ed17e24993ccc7b4b146ce Mon Sep 17 00:00:00 2001 From: BoThe1K Date: Fri, 27 Oct 2023 21:12:09 +0200 Subject: [PATCH 2/2] feat/qb-2037: Update chart scale --- .pnp.cjs | 10 ++++++++++ apps/web-stratos/package.json | 2 ++ .../home/components/charts/TokenomicChart.tsx | 17 ++++++++++++++--- .../src/screens/home/components/hero/index.tsx | 12 ++++++------ .../home/components/tokenomics/index.tsx | 5 ++++- packages/ui/public/locales/en/common.json | 2 +- yarn.lock | 11 +++++++++++ 7 files changed, 48 insertions(+), 11 deletions(-) diff --git a/.pnp.cjs b/.pnp.cjs index 0b172616d7..f84a628d54 100755 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -7654,6 +7654,14 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@types/d3-time", "npm:3.0.0"]\ ],\ "linkType": "HARD"\ + }],\ + ["npm:4.0.6", {\ + "packageLocation": "./.yarn/cache/@types-d3-scale-npm-4.0.6-94fcea076a-1c76c74b03.zip/node_modules/@types/d3-scale/",\ + "packageDependencies": [\ + ["@types/d3-scale", "npm:4.0.6"],\ + ["@types/d3-time", "npm:3.0.0"]\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["@types/d3-shape", [\ @@ -21401,6 +21409,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@testing-library/react", "virtual:9dce388d82c018b4a7af5edc7243e51f7023d1ab93a923b3959d0066ac6881c93b965a0932486426cbefa96c9c8e47849d5ff541d2404b8aca246480fa32f0d2#npm:14.0.0"],\ ["@types/big.js", "npm:6.1.6"],\ ["@types/color", "npm:3.0.3"],\ + ["@types/d3-scale", "npm:4.0.6"],\ ["@types/eslint", "npm:8.37.0"],\ ["@types/esprima", "npm:4.0.3"],\ ["@types/jest", "npm:29.5.1"],\ @@ -21427,6 +21436,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["color", "npm:4.2.3"],\ ["copy-to-clipboard", "npm:3.3.3"],\ ["csstype", "npm:3.1.2"],\ + ["d3-scale", "npm:4.0.2"],\ ["dayjs", "npm:1.11.7"],\ ["dotenv", "npm:16.0.3"],\ ["eslint", "npm:8.40.0"],\ diff --git a/apps/web-stratos/package.json b/apps/web-stratos/package.json index b4188d8ac3..629e747654 100644 --- a/apps/web-stratos/package.json +++ b/apps/web-stratos/package.json @@ -36,6 +36,7 @@ "big.js": "^6.2.1", "color": "^4.2.3", "copy-to-clipboard": "^3.3.3", + "d3-scale": "^4.0.2", "dayjs": "^1.11.7", "ethers": "5.7.2", "framer-motion": "^10.12.8", @@ -94,6 +95,7 @@ "@testing-library/react": "^14.0.0", "@types/big.js": "^6.1.6", "@types/color": "^3.0.3", + "@types/d3-scale": "^4", "@types/eslint": "^8.37.0", "@types/esprima": "^4.0.3", "@types/jest": "^29.5.1", diff --git a/apps/web-stratos/src/screens/home/components/charts/TokenomicChart.tsx b/apps/web-stratos/src/screens/home/components/charts/TokenomicChart.tsx index 07c89e2a27..9397a77aa2 100644 --- a/apps/web-stratos/src/screens/home/components/charts/TokenomicChart.tsx +++ b/apps/web-stratos/src/screens/home/components/charts/TokenomicChart.tsx @@ -1,19 +1,30 @@ import { useId, FC } from 'react'; +import { BigNumber } from 'ethers'; + import { Tooltip, BarChart, CartesianGrid, XAxis, YAxis, Bar } from 'recharts'; interface ITokenomicChart { + total: string; data?: any[]; } -const TokenomicChart: FC = ({ data }) => { +const TokenomicChart: FC = ({ data, total }) => { const chartId = useId(); return ( - + - + diff --git a/apps/web-stratos/src/screens/home/components/hero/index.tsx b/apps/web-stratos/src/screens/home/components/hero/index.tsx index d34771b6bc..9f80d4b2b5 100644 --- a/apps/web-stratos/src/screens/home/components/hero/index.tsx +++ b/apps/web-stratos/src/screens/home/components/hero/index.tsx @@ -1,7 +1,7 @@ import Box from '@/components/box'; import Loading from '@/components/loading'; import OnlineVotingPower from '@/screens/home/components/hero/components/online_voting_power'; -import TokenPrice from '@/screens/home/components/hero/components/token_price'; +// import TokenPrice from '@/screens/home/components/hero/components/token_price'; import { useHero } from '@/screens/home/components/hero/hooks'; import { FC } from 'react'; @@ -9,11 +9,11 @@ const Hero: FC = (props) => { const { state } = useHero(); let component = null; if (!state.loading) { - if (state.tokenPriceHistory.length) { - component = ; - } else { - component = ; - } + // if (state.tokenPriceHistory.length) { + // component = ; + // } else { + component = ; + // } } else { component = ; } diff --git a/apps/web-stratos/src/screens/home/components/tokenomics/index.tsx b/apps/web-stratos/src/screens/home/components/tokenomics/index.tsx index 8885cdac27..1fcc4f2009 100644 --- a/apps/web-stratos/src/screens/home/components/tokenomics/index.tsx +++ b/apps/web-stratos/src/screens/home/components/tokenomics/index.tsx @@ -89,7 +89,10 @@ const Tokenomics: FC = ({ className }) => { ))}
- + x.value !== 0)} + total={prettyFormat(tokenomics.total, 18, 0)} + />
); diff --git a/packages/ui/public/locales/en/common.json b/packages/ui/public/locales/en/common.json index 7708b0a063..e257ff7664 100644 --- a/packages/ui/public/locales/en/common.json +++ b/packages/ui/public/locales/en/common.json @@ -31,7 +31,7 @@ "price": "Price", "inflation": "Inflation", "apr": "APR", - "miningReward": "Mining Reward", + "miningReward": "Chain Reward", "success": "Success", "fail": "Fail", "filterBy": "Filter By...", diff --git a/yarn.lock b/yarn.lock index 4c8434d12f..17af463ac3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5044,6 +5044,15 @@ __metadata: languageName: node linkType: hard +"@types/d3-scale@npm:^4": + version: 4.0.6 + resolution: "@types/d3-scale@npm:4.0.6" + dependencies: + "@types/d3-time": "*" + checksum: 1c76c74b0333e8a1e0a2e45d26a22a448808b5381f72359b4442d02d7c77dae976c03559569e8347635adfa77fe781a70717e331b819064aeea7b9e24028c1aa + languageName: node + linkType: hard + "@types/d3-scale@npm:^4.0.2": version: 4.0.3 resolution: "@types/d3-scale@npm:4.0.3" @@ -15972,6 +15981,7 @@ __metadata: "@testing-library/react": ^14.0.0 "@types/big.js": ^6.1.6 "@types/color": ^3.0.3 + "@types/d3-scale": ^4 "@types/eslint": ^8.37.0 "@types/esprima": ^4.0.3 "@types/jest": ^29.5.1 @@ -15998,6 +16008,7 @@ __metadata: color: ^4.2.3 copy-to-clipboard: ^3.3.3 csstype: ^3.1.2 + d3-scale: ^4.0.2 dayjs: ^1.11.7 dotenv: ^16.0.3 eslint: ^8.40.0