From f218eeda7be662ec13ab08c363a7f3c439f8197e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Garbaci=C5=84ski?= <57113816+kgarbacinski@users.noreply.github.com> Date: Tue, 23 Jul 2024 22:45:16 +0200 Subject: [PATCH 001/241] OCT-1516: Improve processing in an asynchronous way (#353) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …sing ## Description ## Definition of Done 1. [ ] Acceptance criteria are met. 2. [ ] PR is manually tested before the merge by developer(s). - [ ] Happy path is manually checked. 3. [ ] PR is manually tested by QA when their assistance is required (1). - [ ] Octant Areas & Test Cases are checked for impact and updated if required (2). 4. [ ] Unit tests are added unless there is a reason to omit them. 5. [ ] Automated tests are added when required. 6. [ ] The code is merged. 7. [ ] Tech documentation is added / updated, reviewed and approved (including mandatory approval by a code owner, should such exist for changed files). - [ ] BE: Swagger documentation is updated. 8. [ ] When required by QA: - [ ] Deployed to the relevant environment. - [ ] Passed system tests. --- (1) Developer(s) in coordination with QA decide whether it's required. For small tickets introducing small changes QA assistance is most probably not required. (2) [Octant Areas & Test Cases](https://docs.google.com/spreadsheets/d/1cRe6dxuKJV3a4ZskAwWEPvrFkQm6rEfyUCYwLTYw_Cc). --- backend/app/modules/common/synchronized.py | 14 ++++++++++++++ backend/app/modules/facades/confirm_multisig.py | 2 ++ .../multisig_signatures/service/offchain.py | 11 ++++------- 3 files changed, 20 insertions(+), 7 deletions(-) create mode 100644 backend/app/modules/common/synchronized.py diff --git a/backend/app/modules/common/synchronized.py b/backend/app/modules/common/synchronized.py new file mode 100644 index 0000000000..9a242c4d4d --- /dev/null +++ b/backend/app/modules/common/synchronized.py @@ -0,0 +1,14 @@ +from threading import Lock +from functools import wraps +from typing import Callable + + +def synchronized(wrapped: Callable): + lock = Lock() + + @wraps(wrapped) + def _wrap(*args, **kwargs): + with lock: + return wrapped(*args, **kwargs) + + return _wrap diff --git a/backend/app/modules/facades/confirm_multisig.py b/backend/app/modules/facades/confirm_multisig.py index a867db91dc..4281ad2ce4 100644 --- a/backend/app/modules/facades/confirm_multisig.py +++ b/backend/app/modules/facades/confirm_multisig.py @@ -7,8 +7,10 @@ ) from app.modules.user.allocations import controller as allocations_controller from app.modules.user.tos import controller as tos_controller +from app.modules.common.synchronized import synchronized +@synchronized def confirm_multisig(): """ This is a facade function that is used to confirm (i.e approve and apply) multisig approvals. diff --git a/backend/app/modules/multisig_signatures/service/offchain.py b/backend/app/modules/multisig_signatures/service/offchain.py index 6a5c50c141..89bfbc1b31 100644 --- a/backend/app/modules/multisig_signatures/service/offchain.py +++ b/backend/app/modules/multisig_signatures/service/offchain.py @@ -1,4 +1,4 @@ -from typing import List +from typing import List, Dict from app.context.manager import Context from app.exceptions import InvalidMultisigSignatureRequest, InvalidMultisigAddress @@ -6,8 +6,8 @@ from app.infrastructure import database from app.infrastructure.database.models import MultisigSignatures from app.infrastructure.database.multisig_signature import SigStatus, MultisigFilters - from app.infrastructure.external_api.common import retry_request +from app.infrastructure.external_api.safe.message_details import get_message_details from app.modules.common.allocations.deserializer import deserialize_payload from app.modules.common.crypto.eip1271 import get_message_hash from app.modules.common.crypto.signature import ( @@ -23,16 +23,13 @@ ) from app.modules.multisig_signatures.dto import Signature from app.pydantic import Model -from app.infrastructure.external_api.safe.message_details import get_message_details class OffchainMultisigSignatures(Model): is_mainnet: bool = False - verifiers: dict[SignatureOpType, Verifier] + verifiers: Dict[SignatureOpType, Verifier] - staged_signatures: list[ - MultisigSignatures - ] = [] # TODO make it invulnerable for data race & race conditions + staged_signatures: List[MultisigSignatures] = [] def get_last_pending_signature( self, _: Context, user_address: str, op_type: SignatureOpType From 4af8d30e715d08dfbdb35919ce7999ead2fea46f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Kluczek?= Date: Tue, 23 Jul 2024 22:38:09 +0200 Subject: [PATCH 002/241] OCT-1750: introduce redis cache --- backend/app/settings.py | 9 +- backend/poetry.lock | 153 +++++--------------- backend/pyproject.toml | 1 + ci/argocd/templates/octant-application.yaml | 2 +- 4 files changed, 43 insertions(+), 122 deletions(-) diff --git a/backend/app/settings.py b/backend/app/settings.py index 58988673a5..75c37cb388 100644 --- a/backend/app/settings.py +++ b/backend/app/settings.py @@ -28,7 +28,6 @@ class Config(object): GC_PASSPORT_SCORER_ID = os.getenv("GC_PASSPORT_SCORER_ID") GC_PASSPORT_SCORER_API_KEY = os.getenv("GC_PASSPORT_SCORER_API_KEY") SCHEDULER_ENABLED = parse_bool(os.getenv("SCHEDULER_ENABLED")) - CACHE_TYPE = "SimpleCache" DELEGATION_SALT = os.getenv("DELEGATION_SALT") DELEGATION_SALT_PRIMARY = os.getenv("DELEGATION_SALT_PRIMARY") @@ -93,6 +92,11 @@ class ProdConfig(Config): "pool_pre_ping": True, } X_REAL_IP_REQUIRED = parse_bool(os.getenv("X_REAL_IP_REQUIRED", "true")) + CACHE_TYPE = "RedisCache" + CACHE_REDIS_HOST = os.getenv("CACHE_REDIS_HOST") + CACHE_REDIS_PORT = os.getenv("CACHE_REDIS_PORT") + CACHE_REDIS_PASSWORD = os.getenv("CACHE_REDIS_PASSWORD") + CACHE_REDIS_DB = os.getenv("CACHE_REDIS_DB") class DevConfig(Config): @@ -108,6 +112,7 @@ class DevConfig(Config): SQLALCHEMY_DATABASE_URI = f"sqlite:///{DB_PATH}" SUBGRAPH_RETRY_TIMEOUT_SEC = 2 X_REAL_IP_REQUIRED = parse_bool(os.getenv("X_REAL_IP_REQUIRED", "false")) + CACHE_TYPE = "SimpleCache" class ComposeConfig(Config): @@ -117,6 +122,7 @@ class ComposeConfig(Config): DEBUG = True SQLALCHEMY_DATABASE_URI = os.getenv("DB_URI") X_REAL_IP_REQUIRED = parse_bool(os.getenv("X_REAL_IP_REQUIRED", "false")) + CACHE_TYPE = "SimpleCache" class TestConfig(Config): @@ -141,6 +147,7 @@ class TestConfig(Config): DELEGATION_SALT = "salt" DELEGATION_SALT_PRIMARY = "salt_primary" SUBGRAPH_RETRY_TIMEOUT_SEC = 2 + CACHE_TYPE = "SimpleCache" def get_config(): diff --git a/backend/poetry.lock b/backend/poetry.lock index 6aa8c0c014..209ea55a9f 100644 --- a/backend/poetry.lock +++ b/backend/poetry.lock @@ -1,10 +1,9 @@ -# This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. [[package]] name = "aiohttp" version = "3.9.5" description = "Async http client/server framework (asyncio)" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -100,7 +99,6 @@ speedups = ["Brotli", "aiodns", "brotlicffi"] name = "aiosignal" version = "1.3.1" description = "aiosignal: a list of registered asynchronous callbacks" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -115,7 +113,6 @@ frozenlist = ">=1.1.0" name = "alembic" version = "1.13.1" description = "A database migration tool for SQLAlchemy." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -135,7 +132,6 @@ tz = ["backports.zoneinfo"] name = "aniso8601" version = "9.0.1" description = "A library for parsing ISO 8601 strings." -category = "main" optional = false python-versions = "*" files = [ @@ -150,7 +146,6 @@ dev = ["black", "coverage", "isort", "pre-commit", "pyenchant", "pylint"] name = "annotated-types" version = "0.7.0" description = "Reusable constraint types to use with typing.Annotated" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -162,7 +157,6 @@ files = [ name = "anyio" version = "4.4.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -183,7 +177,6 @@ trio = ["trio (>=0.23)"] name = "apscheduler" version = "3.10.4" description = "In-process task scheduler with Cron-like capabilities" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -194,7 +187,7 @@ files = [ [package.dependencies] pytz = "*" six = ">=1.4.0" -tzlocal = ">=2.0,<3.0.0 || >=4.0.0" +tzlocal = ">=2.0,<3.dev0 || >=4.dev0" [package.extras] doc = ["sphinx", "sphinx-rtd-theme"] @@ -208,11 +201,21 @@ tornado = ["tornado (>=4.3)"] twisted = ["twisted"] zookeeper = ["kazoo"] +[[package]] +name = "async-timeout" +version = "4.0.3" +description = "Timeout context manager for asyncio programs" +optional = false +python-versions = ">=3.7" +files = [ + {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, + {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, +] + [[package]] name = "attrs" version = "23.2.0" description = "Classes Without Boilerplate" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -232,7 +235,6 @@ tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "p name = "backoff" version = "2.2.1" description = "Function decoration for backoff and retry" -category = "main" optional = false python-versions = ">=3.7,<4.0" files = [ @@ -244,7 +246,6 @@ files = [ name = "bidict" version = "0.23.1" description = "The bidirectional mapping library for Python." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -256,7 +257,6 @@ files = [ name = "bitarray" version = "2.9.2" description = "efficient arrays of booleans -- C extension" -category = "main" optional = false python-versions = "*" files = [ @@ -388,7 +388,6 @@ files = [ name = "black" version = "23.12.1" description = "The uncompromising code formatter." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -433,7 +432,6 @@ uvloop = ["uvloop (>=0.15.2)"] name = "blinker" version = "1.8.2" description = "Fast, simple object-to-object and broadcast signaling" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -445,7 +443,6 @@ files = [ name = "cachelib" version = "0.9.0" description = "A collection of cache libraries in the same API interface." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -457,7 +454,6 @@ files = [ name = "certifi" version = "2024.6.2" description = "Python package for providing Mozilla's CA Bundle." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -469,7 +465,6 @@ files = [ name = "charset-normalizer" version = "3.3.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "main" optional = false python-versions = ">=3.7.0" files = [ @@ -569,7 +564,6 @@ files = [ name = "ckzg" version = "1.0.2" description = "Python bindings for C-KZG-4844" -category = "main" optional = false python-versions = "*" files = [ @@ -664,7 +658,6 @@ files = [ name = "click" version = "8.1.7" description = "Composable command line interface toolkit" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -679,7 +672,6 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""} name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ @@ -691,7 +683,6 @@ files = [ name = "coverage" version = "7.5.3" description = "Code coverage measurement for Python" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -756,7 +747,6 @@ toml = ["tomli"] name = "cytoolz" version = "0.12.3" description = "Cython implementation of Toolz: High performance functional utilities" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -876,7 +866,6 @@ cython = ["cython"] name = "dataclass-wizard" version = "0.22.3" description = "Marshal dataclasses to/from JSON. Use field properties with initial values. Construct a dataclass schema with JSON input." -category = "main" optional = false python-versions = "*" files = [ @@ -893,7 +882,6 @@ yaml = ["PyYAML (>=5.3)"] name = "dnspython" version = "2.6.1" description = "DNS toolkit" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -914,7 +902,6 @@ wmi = ["wmi (>=1.5.1)"] name = "epc" version = "0.0.5" description = "EPC (RPC stack for Emacs Lisp) implementation in Python" -category = "dev" optional = false python-versions = "*" files = [ @@ -928,7 +915,6 @@ sexpdata = ">=0.0.3" name = "eth-abi" version = "4.2.1" description = "eth_abi: Python utilities for working with Ethereum ABI definitions, especially encoding and decoding" -category = "main" optional = false python-versions = ">=3.7.2, <4" files = [ @@ -952,7 +938,6 @@ tools = ["hypothesis (>=4.18.2,<5.0.0)"] name = "eth-account" version = "0.11.2" description = "eth-account: Sign Ethereum transactions and messages with local private keys" -category = "main" optional = false python-versions = "<4,>=3.8" files = [ @@ -980,7 +965,6 @@ test = ["coverage", "hypothesis (>=4.18.0,<5)", "pytest (>=7.0.0)", "pytest-xdis name = "eth-hash" version = "0.7.0" description = "eth-hash: The Ethereum hashing function, keccak256, sometimes (erroneously) called sha3" -category = "main" optional = false python-versions = ">=3.8, <4" files = [ @@ -1002,7 +986,6 @@ test = ["pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] name = "eth-keyfile" version = "0.8.1" description = "eth-keyfile: A library for handling the encrypted keyfiles used to store ethereum private keys" -category = "main" optional = false python-versions = "<4,>=3.8" files = [ @@ -1024,7 +1007,6 @@ test = ["pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] name = "eth-keys" version = "0.5.1" description = "eth-keys: Common API for Ethereum key operations" -category = "main" optional = false python-versions = "<4,>=3.8" files = [ @@ -1046,7 +1028,6 @@ test = ["asn1tools (>=0.146.2)", "eth-hash[pysha3]", "factory-boy (>=3.0.1)", "h name = "eth-rlp" version = "1.0.1" description = "eth-rlp: RLP definitions for common Ethereum objects in Python" -category = "main" optional = false python-versions = ">=3.8, <4" files = [ @@ -1069,7 +1050,6 @@ test = ["eth-hash[pycryptodome]", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] name = "eth-typing" version = "4.3.1" description = "eth-typing: Common type annotations for ethereum python packages" -category = "main" optional = false python-versions = "<4,>=3.8" files = [ @@ -1089,7 +1069,6 @@ test = ["pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] name = "eth-utils" version = "4.1.1" description = "eth-utils: Common utility functions for python code that interacts with Ethereum" -category = "main" optional = false python-versions = "<4,>=3.8" files = [ @@ -1112,7 +1091,6 @@ test = ["hypothesis (>=4.43.0)", "mypy (==1.5.1)", "pytest (>=7.0.0)", "pytest-x name = "eventlet" version = "0.33.3" description = "Highly concurrent networking library" -category = "main" optional = false python-versions = "*" files = [ @@ -1129,7 +1107,6 @@ six = ">=1.10.0" name = "flake8" version = "6.1.0" description = "the modular source code checker: pep8 pyflakes and co" -category = "dev" optional = false python-versions = ">=3.8.1" files = [ @@ -1146,7 +1123,6 @@ pyflakes = ">=3.1.0,<3.2.0" name = "flake8-bugbear" version = "23.12.2" description = "A plugin for flake8 finding likely bugs and design problems in your program. Contains warnings that don't belong in pyflakes and pycodestyle." -category = "dev" optional = false python-versions = ">=3.8.1" files = [ @@ -1165,7 +1141,6 @@ dev = ["coverage", "hypothesis", "hypothesmith (>=0.2)", "pre-commit", "pytest", name = "flake8-pyproject" version = "1.2.3" description = "Flake8 plug-in loading the configuration from pyproject.toml" -category = "dev" optional = false python-versions = ">= 3.6" files = [ @@ -1182,7 +1157,6 @@ dev = ["pyTest", "pyTest-cov"] name = "flask" version = "2.3.3" description = "A simple framework for building complex web applications." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1205,7 +1179,6 @@ dotenv = ["python-dotenv"] name = "flask-apscheduler" version = "1.13.1" description = "Adds APScheduler support to Flask" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1221,7 +1194,6 @@ python-dateutil = ">=2.4.2" name = "flask-caching" version = "2.3.0" description = "Adds caching support to Flask applications." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1237,7 +1209,6 @@ Flask = "*" name = "flask-cors" version = "4.0.1" description = "A Flask extension adding a decorator for CORS support" -category = "main" optional = false python-versions = "*" files = [ @@ -1252,7 +1223,6 @@ Flask = ">=0.9" name = "flask-migrate" version = "4.0.7" description = "SQLAlchemy database migrations for Flask applications using Alembic." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1269,7 +1239,6 @@ Flask-SQLAlchemy = ">=1.0" name = "flask-restx" version = "1.3.0" description = "Fully featured framework for fast, easy and documented API development with Flask" -category = "main" optional = false python-versions = "*" files = [ @@ -1294,7 +1263,6 @@ test = ["Faker (==2.0.0)", "blinker", "invoke (==2.2.0)", "mock (==3.0.5)", "pyt name = "flask-socketio" version = "5.3.6" description = "Socket.IO integration for Flask applications" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1313,7 +1281,6 @@ docs = ["sphinx"] name = "flask-sqlalchemy" version = "3.1.1" description = "Add SQLAlchemy support to your Flask application." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1329,7 +1296,6 @@ sqlalchemy = ">=2.0.16" name = "freezegun" version = "1.5.1" description = "Let your Python tests travel through time" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1344,7 +1310,6 @@ python-dateutil = ">=2.7" name = "frozenlist" version = "1.4.1" description = "A list-like structure which implements collections.abc.MutableSequence" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1431,7 +1396,6 @@ files = [ name = "gmpy2" version = "2.1.5" description = "gmpy2 interface to GMP/MPIR, MPFR, and MPC for Python 2.7 and 3.5+" -category = "main" optional = false python-versions = "*" files = [ @@ -1492,7 +1456,6 @@ files = [ name = "gql" version = "3.5.0" description = "GraphQL client for Python" -category = "main" optional = false python-versions = "*" files = [ @@ -1525,7 +1488,6 @@ websockets = ["websockets (>=10,<12)"] name = "graphql-core" version = "3.2.3" description = "GraphQL implementation for Python, a port of GraphQL.js, the JavaScript reference implementation for GraphQL." -category = "main" optional = false python-versions = ">=3.6,<4" files = [ @@ -1537,7 +1499,6 @@ files = [ name = "greenlet" version = "3.0.3" description = "Lightweight in-process concurrent programming" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1609,7 +1570,6 @@ test = ["objgraph", "psutil"] name = "h11" version = "0.14.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1621,7 +1581,6 @@ files = [ name = "hexbytes" version = "0.3.1" description = "hexbytes: Python `bytes` subclass that decodes hex, with a readable console output" -category = "main" optional = false python-versions = ">=3.7, <4" files = [ @@ -1639,7 +1598,6 @@ test = ["eth-utils (>=1.0.1,<3)", "hypothesis (>=3.44.24,<=6.31.6)", "pytest (>= name = "idna" version = "3.7" description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -1651,7 +1609,6 @@ files = [ name = "importlib-resources" version = "6.4.0" description = "Read resources from Python packages" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1667,7 +1624,6 @@ testing = ["jaraco.test (>=5.4)", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "p name = "importmagic" version = "0.1.7" description = "Python Import Magic - automagically add, remove and manage imports" -category = "dev" optional = false python-versions = "*" files = [ @@ -1681,7 +1637,6 @@ setuptools = ">=0.6b1" name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1693,7 +1648,6 @@ files = [ name = "itsdangerous" version = "2.2.0" description = "Safely pass data to untrusted environments and back." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1705,7 +1659,6 @@ files = [ name = "jinja2" version = "3.1.4" description = "A very fast and expressive template engine." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1723,7 +1676,6 @@ i18n = ["Babel (>=2.7)"] name = "jsonschema" version = "4.17.3" description = "An implementation of JSON Schema validation for Python" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1743,7 +1695,6 @@ format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339- name = "lru-dict" version = "1.2.0" description = "An Dict like LRU container." -category = "main" optional = false python-versions = "*" files = [ @@ -1838,7 +1789,6 @@ test = ["pytest"] name = "mako" version = "1.3.5" description = "A super-fast templating language that borrows the best ideas from the existing templating languages." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1858,7 +1808,6 @@ testing = ["pytest"] name = "markupsafe" version = "2.1.5" description = "Safely add untrusted strings to HTML/XML markup." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1928,7 +1877,6 @@ files = [ name = "mccabe" version = "0.7.0" description = "McCabe checker, plugin for flake8" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -1940,7 +1888,6 @@ files = [ name = "multidict" version = "6.0.5" description = "multidict implementation" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2040,7 +1987,6 @@ files = [ name = "multiproof" version = "0.1.2" description = "A Python library to generate merkle trees and merkle proofs." -category = "main" optional = false python-versions = "^3.10" files = [] @@ -2060,7 +2006,6 @@ resolved_reference = "e1f3633a10cb5929cc08d4f261effd170976e7b9" name = "mypy-extensions" version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -2072,7 +2017,6 @@ files = [ name = "nodeenv" version = "1.9.1" description = "Node.js virtual environment builder" -category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ @@ -2084,7 +2028,6 @@ files = [ name = "numpy" version = "2.0.0" description = "Fundamental package for array computing in Python" -category = "main" optional = false python-versions = ">=3.9" files = [ @@ -2139,7 +2082,6 @@ files = [ name = "packaging" version = "24.1" description = "Core utilities for Python packages" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2151,7 +2093,6 @@ files = [ name = "pandas" version = "2.2.2" description = "Powerful data structures for data analysis, time series, and statistics" -category = "main" optional = false python-versions = ">=3.9" files = [ @@ -2224,7 +2165,6 @@ xml = ["lxml (>=4.9.2)"] name = "parsimonious" version = "0.9.0" description = "(Soon to be) the fastest pure-Python PEG parser I could muster" -category = "main" optional = false python-versions = "*" files = [ @@ -2238,7 +2178,6 @@ regex = ">=2022.3.15" name = "pathspec" version = "0.12.1" description = "Utility library for gitignore style pattern matching of file paths." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2250,7 +2189,6 @@ files = [ name = "platformdirs" version = "4.2.2" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2267,7 +2205,6 @@ type = ["mypy (>=1.8)"] name = "pluggy" version = "1.5.0" description = "plugin and hook calling mechanisms for python" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2283,7 +2220,6 @@ testing = ["pytest", "pytest-benchmark"] name = "protobuf" version = "5.27.1" description = "" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -2304,7 +2240,6 @@ files = [ name = "psycopg2-binary" version = "2.9.9" description = "psycopg2 - Python-PostgreSQL Database Adapter" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2386,7 +2321,6 @@ files = [ name = "pycodestyle" version = "2.11.1" description = "Python style guide checker" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2398,7 +2332,6 @@ files = [ name = "pycryptodome" version = "3.20.0" description = "Cryptographic library for Python" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -2440,7 +2373,6 @@ files = [ name = "pydantic" version = "2.7.4" description = "Data validation using Python type hints" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -2460,7 +2392,6 @@ email = ["email-validator (>=2.0.0)"] name = "pydantic-core" version = "2.18.4" description = "Core functionality for Pydantic validation and serialization" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -2552,7 +2483,6 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" name = "pyflakes" version = "3.1.0" description = "passive checker of Python programs" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2564,7 +2494,6 @@ files = [ name = "pylookup" version = "0.2.2" description = "PyLookup - Fuzzy-matching table autofill tool" -category = "dev" optional = false python-versions = "*" files = [ @@ -2581,7 +2510,6 @@ rapidfuzz = "*" name = "pyright" version = "1.1.368" description = "Command line wrapper for pyright" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2600,7 +2528,6 @@ dev = ["twine (>=3.4.1)"] name = "pyrsistent" version = "0.20.0" description = "Persistent/Functional/Immutable data structures" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -2642,7 +2569,6 @@ files = [ name = "pytest" version = "7.4.4" description = "pytest: simple powerful testing with Python" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2663,7 +2589,6 @@ testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "no name = "pytest-cov" version = "4.1.0" description = "Pytest plugin for measuring coverage." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2682,7 +2607,6 @@ testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtuale name = "pytest-mock" version = "3.14.0" description = "Thin-wrapper around the mock package for easier use with pytest" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2700,7 +2624,6 @@ dev = ["pre-commit", "pytest-asyncio", "tox"] name = "python-dateutil" version = "2.9.0.post0" description = "Extensions to the standard Python datetime module" -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ @@ -2715,7 +2638,6 @@ six = ">=1.5" name = "python-dotenv" version = "1.0.1" description = "Read key-value pairs from a .env file and set them as environment variables" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -2730,7 +2652,6 @@ cli = ["click (>=5.0)"] name = "python-engineio" version = "4.9.1" description = "Engine.IO server and client for Python" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -2750,7 +2671,6 @@ docs = ["sphinx"] name = "python-socketio" version = "5.11.3" description = "Socket.IO server and client for Python" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -2771,7 +2691,6 @@ docs = ["sphinx"] name = "pytz" version = "2024.1" description = "World timezone definitions, modern and historical" -category = "main" optional = false python-versions = "*" files = [ @@ -2783,7 +2702,6 @@ files = [ name = "pyunormalize" version = "15.1.0" description = "Unicode normalization forms (NFC, NFKC, NFD, NFKD). A library independent from the Python core Unicode database." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -2794,7 +2712,6 @@ files = [ name = "pywin32" version = "306" description = "Python for Window Extensions" -category = "main" optional = false python-versions = "*" files = [ @@ -2818,7 +2735,6 @@ files = [ name = "rapidfuzz" version = "3.9.3" description = "rapid fuzzy string matching" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2920,11 +2836,28 @@ files = [ [package.extras] full = ["numpy"] +[[package]] +name = "redis" +version = "5.0.7" +description = "Python client for Redis database and key-value store" +optional = false +python-versions = ">=3.7" +files = [ + {file = "redis-5.0.7-py3-none-any.whl", hash = "sha256:0e479e24da960c690be5d9b96d21f7b918a98c0cf49af3b6fafaa0753f93a0db"}, + {file = "redis-5.0.7.tar.gz", hash = "sha256:8f611490b93c8109b50adc317b31bfd84fff31def3475b92e7e80bf39f48175b"}, +] + +[package.dependencies] +async-timeout = {version = ">=4.0.3", markers = "python_full_version < \"3.11.3\""} + +[package.extras] +hiredis = ["hiredis (>=1.0.0)"] +ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==20.0.1)", "requests (>=2.26.0)"] + [[package]] name = "regex" version = "2024.5.15" description = "Alternative regular expression module, to replace re." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -3013,7 +2946,6 @@ files = [ name = "requests" version = "2.32.3" description = "Python HTTP for Humans." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -3035,7 +2967,6 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] name = "requests-toolbelt" version = "1.0.0" description = "A utility belt for advanced users of python-requests" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -3050,7 +2981,6 @@ requests = ">=2.0.1,<3.0.0" name = "rlp" version = "4.0.1" description = "rlp: A package for Recursive Length Prefix encoding and decoding" -category = "main" optional = false python-versions = "<4,>=3.8" files = [ @@ -3071,7 +3001,6 @@ test = ["hypothesis (==5.19.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] name = "sentry-sdk" version = "2.6.0" description = "Python client for Sentry (https://sentry.io)" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -3125,7 +3054,6 @@ tornado = ["tornado (>=5)"] name = "setuptools" version = "70.1.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -3141,7 +3069,6 @@ testing = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metad name = "sexpdata" version = "1.0.2" description = "S-expression parser for Python" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3153,7 +3080,6 @@ files = [ name = "simple-websocket" version = "1.0.0" description = "Simple WebSocket server and client for Python" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -3171,7 +3097,6 @@ docs = ["sphinx"] name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -3183,7 +3108,6 @@ files = [ name = "sniffio" version = "1.3.1" description = "Sniff out which async library your code is running under" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3195,7 +3119,6 @@ files = [ name = "sqlalchemy" version = "2.0.31" description = "Database Abstraction Library" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3251,7 +3174,7 @@ files = [ ] [package.dependencies] -greenlet = {version = "!=0.4.17", markers = "python_version < \"3.13\" and platform_machine == \"aarch64\" or python_version < \"3.13\" and platform_machine == \"ppc64le\" or python_version < \"3.13\" and platform_machine == \"x86_64\" or python_version < \"3.13\" and platform_machine == \"amd64\" or python_version < \"3.13\" and platform_machine == \"AMD64\" or python_version < \"3.13\" and platform_machine == \"win32\" or python_version < \"3.13\" and platform_machine == \"WIN32\""} +greenlet = {version = "!=0.4.17", markers = "python_version < \"3.13\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"} typing-extensions = ">=4.6.0" [package.extras] @@ -3283,7 +3206,6 @@ sqlcipher = ["sqlcipher3_binary"] name = "toolz" version = "0.12.1" description = "List processing tools and functional utilities" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3295,7 +3217,6 @@ files = [ name = "typing-extensions" version = "4.12.2" description = "Backported and Experimental Type Hints for Python 3.8+" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -3307,7 +3228,6 @@ files = [ name = "tzdata" version = "2024.1" description = "Provider of IANA time zone data" -category = "main" optional = false python-versions = ">=2" files = [ @@ -3319,7 +3239,6 @@ files = [ name = "tzlocal" version = "5.2" description = "tzinfo object for the local timezone" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -3337,7 +3256,6 @@ devenv = ["check-manifest", "pytest (>=4.3)", "pytest-cov", "pytest-mock (>=3.3) name = "urllib3" version = "2.2.2" description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -3355,7 +3273,6 @@ zstd = ["zstandard (>=0.18.0)"] name = "web3" version = "6.19.0" description = "web3.py" -category = "main" optional = false python-versions = ">=3.7.2" files = [ @@ -3390,7 +3307,6 @@ tester = ["eth-tester[py-evm] (>=0.11.0b1,<0.12.0b1)", "eth-tester[py-evm] (>=0. name = "websockets" version = "12.0" description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -3472,7 +3388,6 @@ files = [ name = "werkzeug" version = "3.0.3" description = "The comprehensive WSGI web application library." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -3490,7 +3405,6 @@ watchdog = ["watchdog (>=2.3)"] name = "wsproto" version = "1.2.0" description = "WebSockets state-machine based protocol implementation" -category = "main" optional = false python-versions = ">=3.7.0" files = [ @@ -3505,7 +3419,6 @@ h11 = ">=0.9.0,<1" name = "yarl" version = "1.9.4" description = "Yet another URL library" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3608,4 +3521,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "cc98480e58adeae7f2c86a6a5ead4e27e1844c026f069cae21d1e19f4d2937b5" +content-hash = "8beb2e0b06481e87b431a937a21c11d21f06810f5b7497781c4be087d48e4b44" diff --git a/backend/pyproject.toml b/backend/pyproject.toml index bdf79fe88d..683adf29af 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -28,6 +28,7 @@ pydantic = "^2.6.0" pandas = "^2.2.0" gmpy2 = "^2.1.5" sentry-sdk = {extras = ["flask"], version = "^2.5.1"} +redis = "^5.0.7" [tool.poetry.group.dev.dependencies] pytest = "^7.3.1" diff --git a/ci/argocd/templates/octant-application.yaml b/ci/argocd/templates/octant-application.yaml index 1bd39fb62b..6cde872184 100644 --- a/ci/argocd/templates/octant-application.yaml +++ b/ci/argocd/templates/octant-application.yaml @@ -15,7 +15,7 @@ spec: namespace: $DEPLOYMENT_ID sources: - repoURL: 'https://gitlab.com/api/v4/projects/48137258/packages/helm/devel' - targetRevision: 0.2.55 + targetRevision: 0.2.56 chart: octant helm: parameters: From 77fb77fc9d0c56f6dde40594bd54e1baf49d7fd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Kluczek?= Date: Tue, 23 Jul 2024 23:25:36 +0200 Subject: [PATCH 003/241] trigger CI From 77741ec7246123056ee8ed6987f25ded4f11d8d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Garbaci=C5=84ski?= <57113816+kgarbacinski@users.noreply.github.com> Date: Wed, 24 Jul 2024 11:53:36 +0200 Subject: [PATCH 004/241] OCT-1768 & OCT-1767: Change the verifier logic for E4 (#344) ## Description ## Definition of Done 1. [ ] Acceptance criteria are met. 2. [ ] PR is manually tested before the merge by developer(s). - [ ] Happy path is manually checked. 3. [ ] PR is manually tested by QA when their assistance is required (1). - [ ] Octant Areas & Test Cases are checked for impact and updated if required (2). 4. [ ] Unit tests are added unless there is a reason to omit them. 5. [ ] Automated tests are added when required. 6. [ ] The code is merged. 7. [ ] Tech documentation is added / updated, reviewed and approved (including mandatory approval by a code owner, should such exist for changed files). - [ ] BE: Swagger documentation is updated. 8. [ ] When required by QA: - [ ] Deployed to the relevant environment. - [ ] Passed system tests. --- (1) Developer(s) in coordination with QA decide whether it's required. For small tickets introducing small changes QA assistance is most probably not required. (2) [Octant Areas & Test Cases](https://docs.google.com/spreadsheets/d/1cRe6dxuKJV3a4ZskAwWEPvrFkQm6rEfyUCYwLTYw_Cc). --------- Co-authored-by: adam-gf --- .../src/_deprecated/epoch3/data/context.ts | 63 +++++++ .../src/_deprecated/epoch3/data/fetcher.ts | 70 ++++++++ .../src/_deprecated/epoch3/data/models.ts | 158 ++++++++++++++++++ .../src/_deprecated/epoch3/runner.ts | 69 ++++++++ .../src/_deprecated/epoch3/verifications.ts | 28 ++++ .../epoch3/verifications/budgets.ts | 25 +++ .../epoch3/verifications/donations.ts | 85 ++++++++++ .../_deprecated/epoch3/verifications/utils.ts | 54 ++++++ .../src/_deprecated/epoch3/verifier.ts | 84 ++++++++++ epoch-verifier/src/data/context.ts | 44 ++++- epoch-verifier/src/data/fetcher.ts | 26 +-- epoch-verifier/src/data/models.ts | 44 +++-- epoch-verifier/src/runner.ts | 26 +-- epoch-verifier/src/verifications.ts | 18 +- epoch-verifier/src/verifications/donations.ts | 115 ++++++++----- epoch-verifier/src/verifications/utils.ts | 50 ++++-- epoch-verifier/src/verifier.ts | 10 +- 17 files changed, 861 insertions(+), 108 deletions(-) create mode 100644 epoch-verifier/src/_deprecated/epoch3/data/context.ts create mode 100644 epoch-verifier/src/_deprecated/epoch3/data/fetcher.ts create mode 100644 epoch-verifier/src/_deprecated/epoch3/data/models.ts create mode 100644 epoch-verifier/src/_deprecated/epoch3/runner.ts create mode 100644 epoch-verifier/src/_deprecated/epoch3/verifications.ts create mode 100644 epoch-verifier/src/_deprecated/epoch3/verifications/budgets.ts create mode 100644 epoch-verifier/src/_deprecated/epoch3/verifications/donations.ts create mode 100644 epoch-verifier/src/_deprecated/epoch3/verifications/utils.ts create mode 100644 epoch-verifier/src/_deprecated/epoch3/verifier.ts diff --git a/epoch-verifier/src/_deprecated/epoch3/data/context.ts b/epoch-verifier/src/_deprecated/epoch3/data/context.ts new file mode 100644 index 0000000000..034dcda75c --- /dev/null +++ b/epoch-verifier/src/_deprecated/epoch3/data/context.ts @@ -0,0 +1,63 @@ +import { + Address, + Allocation, + AllocationRecord, + EpochInfo, + Reward, + UserBudget, + UserDonation +} from "./models" + +export interface Context { + allocations: Allocation[] + budgets: Map + epochInfo: EpochInfo + rewards: Reward[] +} + +function transformToAllocations(allocationRecords: AllocationRecord[]): Allocation[] { + const allocations: Map = new Map() + + for (const record of allocationRecords) { + const prev = allocations.get(record.user) ?? { donations: [], user: record.user } + prev.donations.push({ amount: record.amount, proposal: record.proposal }) + allocations.set(record.user, prev) + } + + return Array.from(allocations.values()) +} + +export function buildContext(userBudgets: UserBudget[], allocations: AllocationRecord[], rewards: Reward[], epochInfo: EpochInfo): Context { + + const positiveUserBudgets = userBudgets.filter(positiveUserBudget => positiveUserBudget.amount !== BigInt(0)); + + return { + allocations: transformToAllocations(allocations), + budgets: new Map(positiveUserBudgets.map(value => [value.user, value.amount] as const)), + epochInfo, + rewards + } +} + +export function allocationsByUser(context: Context): Map { + return new Map(context.allocations.map((alloc) => [alloc.user, alloc.donations] as const)) +} + +export function individualDonationsByProjects(context: Context): Map { + const individualDonations: Map = new Map() + const donations: UserDonation[] = context.allocations.flatMap((alloc) => alloc.donations) + + for (const donation of donations) { + const prev = individualDonations.get(donation.proposal) ?? BigInt(0) + individualDonations.set(donation.proposal, prev + donation.amount) + } + + return individualDonations +} + +export function rewardsByProject(context: Context): Map { + return new Map(context.rewards + .filter(r => r.matched !== BigInt(0)) + .map((r) => [r.proposal, r] as const) + ); +} diff --git a/epoch-verifier/src/_deprecated/epoch3/data/fetcher.ts b/epoch-verifier/src/_deprecated/epoch3/data/fetcher.ts new file mode 100644 index 0000000000..81102b0179 --- /dev/null +++ b/epoch-verifier/src/_deprecated/epoch3/data/fetcher.ts @@ -0,0 +1,70 @@ +/* eslint-disable no-console */ +import { Axios } from "axios" + +import { + AllocationImpl, + Deserializable, + EpochInfo, + EpochInfoImpl, + RewardImpl, + Reward, + UserBudgetImpl, + UserBudget, + AllocationRecord, + ApiRewardsBudgets, ApiAllocations, ApiRewards, +} from "./models"; + +const REQUEST_TIMEOUT = 150_000; + +export class HttpFetcher { + readonly axios: Axios + + constructor(baseUrl: string) { + this.axios = new Axios({ baseURL: baseUrl, timeout: REQUEST_TIMEOUT }) + } + + async _get(url: string, resource: string, Factory: { new(): Deserializable }): Promise { + const mapper = (data: any): T => new Factory().from(JSON.parse(data)) + return this._do_get(url, resource, mapper) + } + + async _get_array(url: string, resource: string, Factory: { new(): Deserializable }, unwrapper?: (data: any) => any): Promise | null> { + + const dataUnwrapper = unwrapper ?? ((data: any) => data) + + const mapper = (data: any): Array => dataUnwrapper(JSON.parse(data)).map((elem: any): T => new Factory().from(elem)) + return this._do_get(url, resource, mapper) + } + + async _do_get(url: string, resource: string, mapper: (data: any) => T): Promise { + + return this.axios.get(url).then(response => { + if (response.status !== 200) { + throw new Error(response.data) + } + console.log(`✅ Fetched ${resource} ${response.data}`) + return mapper(response.data) + + }).catch(reason => { + console.error(`❗ Failed to fetch ${resource} due to: ${reason}`) + return null + }) + } + + + async apiGetUserBudgets(epoch: number): Promise { + return this._get_array(`/rewards/budgets/epoch/${epoch}`, "users' budgets", UserBudgetImpl, (data: ApiRewardsBudgets) => data.budgets) + } + + async apiGetAllocations(epoch: number): Promise { + return this._get_array(`/allocations/epoch/${epoch}?includeZeroAllocations=true`, "users' allocations", AllocationImpl, (data: ApiAllocations) => data.allocations) + } + + async apiGetRewards(epoch: number): Promise { + return this._get_array(`/rewards/projects/epoch/${epoch}`, "projects rewards", RewardImpl, (data: ApiRewards) => data.rewards) + } + + async apiGetEpochInfo(epoch: number): Promise { + return this._get(`/epochs/info/${epoch}`, "epoch info", EpochInfoImpl) + } +} diff --git a/epoch-verifier/src/_deprecated/epoch3/data/models.ts b/epoch-verifier/src/_deprecated/epoch3/data/models.ts new file mode 100644 index 0000000000..d99b2d5210 --- /dev/null +++ b/epoch-verifier/src/_deprecated/epoch3/data/models.ts @@ -0,0 +1,158 @@ +/* eslint-disable max-classes-per-file */ +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ +export type Address = string; + +export interface Deserializable { + from(input: any): T; +} + +export interface ApiRewardsBudgets { + budgets: { + address: string; + amount: string; // wei; + }[]; +} + +export interface ApiAllocations { + allocations: { + amount: string; // wei + donor: string; + proposal: string; + }[]; +} + +export interface ApiRewards { + rewards: { + address: string; + value: string; // wei; + }[]; +} + + +export interface UserBudget { + amount: bigint; + user: Address; +} + +export interface UserDonation { + amount: bigint; + proposal: Address; +} + +export interface AllocationRecord { + amount: bigint; + proposal: Address; + user: Address; +} + +export interface Allocation { + donations: UserDonation[]; + user: Address; +} + +export interface Reward { + allocated: bigint; + matched: bigint; + proposal: Address; +} + +export interface EpochInfo { + communityFund: bigint; + individualRewards: bigint; + leftover: bigint; + matchedRewards: bigint; + operationalCost: bigint; + patronsRewards: bigint; + ppf: bigint; + stakingProceeds: bigint; + totalEffectiveDeposit: bigint; + totalRewards: bigint; + totalWithdrawals: bigint; +} + +export class UserBudgetImpl implements Deserializable { + user: Address; + + amount: bigint; + + from(input: any) { + this.user = input.address; + this.amount = BigInt(input.amount) + + return this + } +} + +export class AllocationImpl implements Deserializable { + user: Address; + + proposal: Address; + + amount: bigint; + + from(input: any) { + this.user = input.donor + this.proposal = input.proposal; + this.amount = BigInt(input.amount ?? 0) + + return this + } +} + +export class RewardImpl implements Deserializable { + allocated: bigint; + + matched: bigint; + + proposal: Address; + + from(input: any) { + this.proposal = input.address + this.allocated = BigInt(input.allocated) + this.matched = BigInt(input.matched) + + return this + } +} + +export class EpochInfoImpl implements Deserializable { + individualRewards: bigint; + + matchedRewards: bigint; + + patronsRewards: bigint; + + stakingProceeds: bigint; + + totalEffectiveDeposit: bigint; + + totalRewards: bigint; + + totalWithdrawals: bigint; + + operationalCost: bigint; + + leftover: bigint; + + ppf: bigint; + + communityFund: bigint; + + + from(input: any) { + this.individualRewards = BigInt(input.vanillaIndividualRewards) + this.matchedRewards = BigInt(input.matchedRewards) + this.patronsRewards = BigInt(input.patronsRewards) + this.stakingProceeds = BigInt(input.stakingProceeds) + this.totalEffectiveDeposit = BigInt(input.totalEffectiveDeposit) + this.totalRewards = BigInt(input.totalRewards) + this.totalWithdrawals = BigInt(input.totalWithdrawals) + this.operationalCost = BigInt(input.operationalCost) + this.leftover = BigInt(input.leftover) + this.ppf = BigInt(input.ppf) + this.communityFund = BigInt(input.communityFund) + + return this + } +} + diff --git a/epoch-verifier/src/_deprecated/epoch3/runner.ts b/epoch-verifier/src/_deprecated/epoch3/runner.ts new file mode 100644 index 0000000000..ce24ac80d8 --- /dev/null +++ b/epoch-verifier/src/_deprecated/epoch3/runner.ts @@ -0,0 +1,69 @@ +/* eslint-disable no-console */ +import { Context } from "./data/context" + +export type VerificationResult = { message: string, result: boolean } +type VerifyFunc = (context: Context) => VerificationResult; + +export interface Verification { + readonly name: string + verify: VerifyFunc +} + +export class Runner { + + verifications: Verification[] + + constructor() { + this.verifications = [] + } + + register(verification: Verification): void { + this.verifications.push(verification) + } + + async run(context: Context): Promise { + console.log("Starting verifications ... ") + + const results = await Promise.all(this.verifications.map((v: Verification) => { + + const task = new Promise<[string, VerificationResult]>((resolve, _) => { + console.log(`Verification ${v.name} started`) + const result = v.verify(context) + + if (result.result) { + console.log(`Verification ${v.name} succeeded`) + } + + resolve([v.name, result]) + }) + + const timeout = new Promise((_, reject) => { + setTimeout(() => { + reject(new Error(`Task ${v.name} timed out`)) + }, 15_000) + }) + + return Promise.race<[string, VerificationResult] | never>([task, timeout]) + + })) + + + console.log("Run all verifications") + + const ok = results.filter(([_, result]) => result.result) + const failures = results.filter(([_, result]) => !result.result) + + ok.forEach(([name, result]) => { + console.log(`✅ ${name}: ${result.message}`) + }) + + failures.forEach(([name, result]) => { + console.log(`❗ ${name}: ${result.message}`) + }) + + console.log(`Verifications have finished with ${ok.length} successes and ${failures.length} failures`) + + return failures.length + } + +} diff --git a/epoch-verifier/src/_deprecated/epoch3/verifications.ts b/epoch-verifier/src/_deprecated/epoch3/verifications.ts new file mode 100644 index 0000000000..90f1e88f14 --- /dev/null +++ b/epoch-verifier/src/_deprecated/epoch3/verifications.ts @@ -0,0 +1,28 @@ +import { Runner, Verification } from "./runner"; +import { verifyBudgetsAreEqualToIndividualRewardsPlusHalfPpf, compareUserBudgetsVsTheirAllocations } from "./verifications/budgets"; +import { + verifyMatchedFunds, + verifyProjectsBelowThreshold, + verifyTotalWithdrawals, + verifyRewardsVsUserDonations, + verifyUserDonationsVsRewards, + verifyMatchingFundFromEpochInfo, + verifyAllEpochRewards +} from "./verifications/donations"; + +export function registerVerifications(runner: Runner): void { + + const verifications: Verification[] = [ + { name: "User budgets vs allocations sums", verify: compareUserBudgetsVsTheirAllocations }, + { name: "User budgets vs half PPF", verify: verifyBudgetsAreEqualToIndividualRewardsPlusHalfPpf }, + { name: "Projects below threshold", verify: verifyProjectsBelowThreshold }, + { name: "Users allocated vs rewards allocated", verify: verifyUserDonationsVsRewards }, + { name: "Users rewards allocated vs allocated", verify: verifyRewardsVsUserDonations }, + { name: "Matched funds from allocations vs matched from rewards", verify: verifyMatchedFunds }, + { name: "Total withdrawals vs budgets, allocations, patrons and unclaimed", verify: verifyTotalWithdrawals }, + { name: "Matched funds from epoch info", verify: verifyMatchingFundFromEpochInfo }, + { name: "Total amounts from epoch info", verify: verifyAllEpochRewards }, + ] + + verifications.forEach((v) => runner.register(v)) +} diff --git a/epoch-verifier/src/_deprecated/epoch3/verifications/budgets.ts b/epoch-verifier/src/_deprecated/epoch3/verifications/budgets.ts new file mode 100644 index 0000000000..4d5a5de7e6 --- /dev/null +++ b/epoch-verifier/src/_deprecated/epoch3/verifications/budgets.ts @@ -0,0 +1,25 @@ +import { assertAll, assertEq } from "./utils"; + +import { Context } from "../data/context"; +import { VerificationResult } from "../runner"; + +export function compareUserBudgetsVsTheirAllocations(context: Context): VerificationResult { + + const sums = context.allocations.map((user_alloc) => + [ + user_alloc.user, + user_alloc.donations.reduce((acc, donation) => acc + donation.amount, BigInt(0)) + ] as const + ) + + return assertAll(sums, ([user, sum]) => + sum <= (context.budgets.get(user) ?? BigInt(0)) + ) + +} + +export function verifyBudgetsAreEqualToIndividualRewardsPlusHalfPpf(context: Context): VerificationResult { + const budgets = Array.from(context.budgets.entries()).reduce((acc, [_user, budget]) => acc + budget, BigInt(0)) + const expectedBudgets = context.epochInfo.individualRewards + context.epochInfo.ppf / BigInt(2) + return assertEq(budgets, expectedBudgets, BigInt(500), true) +} diff --git a/epoch-verifier/src/_deprecated/epoch3/verifications/donations.ts b/epoch-verifier/src/_deprecated/epoch3/verifications/donations.ts new file mode 100644 index 0000000000..fbc4e0f64f --- /dev/null +++ b/epoch-verifier/src/_deprecated/epoch3/verifications/donations.ts @@ -0,0 +1,85 @@ +import { assertAll, assertEq } from "./utils"; + +import { Context, allocationsByUser, individualDonationsByProposals, rewardsByProject } from "../data/context"; +import { Address, Reward } from "../data/models"; +import { VerificationResult } from "../runner"; + +const PROPOSALS_NO = 30 + +function getThreshold(individualAllocations: Map): bigint { + const allocationsSum = Array.from(individualAllocations.entries()).reduce((acc, [_, val]) => acc + val, BigInt(0)) + return allocationsSum / BigInt(PROPOSALS_NO) +} + +function getUserAllocationsForProjectsAboveThreshold(context: Context): Map { + const individualDonations = individualDonationsByProposals(context) + const threshold = getThreshold(individualDonations) + const projectsAboveThreshold = Array.from(individualDonations.entries()).filter(([_, v]) => v > threshold) + return new Map(projectsAboveThreshold) +} + +export function verifyProjectsBelowThreshold(context: Context): VerificationResult { + + const individualDonations = individualDonationsByProposals(context) + const threshold = getThreshold(individualDonations) + + const projectsBelowThreshold = Array.from(individualDonations.entries()).filter(([_, v]) => v <= threshold).map(([p, _]) => p) + const rewards = rewardsByProject(context) + + return assertAll(projectsBelowThreshold, (project) => !rewards.has(project)) +} + +export function verifyUserDonationsVsRewards(context: Context): VerificationResult { + const projectsAboveThreshold = Array.from(getUserAllocationsForProjectsAboveThreshold(context).entries()) + const rewards = rewardsByProject(context) + + return assertAll(projectsAboveThreshold, ([proposal, allocated]) => assertEq(allocated, rewards.get(proposal)!.allocated, BigInt(100), true)) +} + +export function verifyRewardsVsUserDonations(context: Context): VerificationResult { + const projectsAboveThreshold = getUserAllocationsForProjectsAboveThreshold(context) + const rewards = Array.from(rewardsByProject(context).entries()) + + return assertAll(rewards, ([proposal, reward]: [Address, Reward]) => assertEq(reward.allocated, projectsAboveThreshold.get(proposal)!, BigInt(100), true)) + +} + +export function verifyMatchedFunds(context: Context): VerificationResult { + const projectsAboveThreshold = Array.from(getUserAllocationsForProjectsAboveThreshold(context).entries()) + const rewards = rewardsByProject(context) + + const totalAllocations = projectsAboveThreshold.reduce((acc, [_, v]) => acc + v, BigInt(0)) + const matchingFund = context.epochInfo.matchedRewards + + return assertAll(projectsAboveThreshold, ([proposal, allocated]) => { + const matched = matchingFund * allocated / totalAllocations + return assertEq(matched, rewards.get(proposal)!.matched, BigInt(100), true) + }) + +} + +export function verifyMatchingFundFromEpochInfo(context: Context): VerificationResult { + const verifyMatchedRewards = [context.epochInfo.totalRewards - context.epochInfo.ppf - context.epochInfo.individualRewards + context.epochInfo.patronsRewards, context.epochInfo.matchedRewards] + + return assertAll([verifyMatchedRewards], ([value, expected]) => assertEq(value, expected)) +} + +export function verifyAllEpochRewards(context: Context): VerificationResult { + const allBudgets = context.epochInfo.individualRewards + context.epochInfo.ppf - context.epochInfo.patronsRewards + context.epochInfo.matchedRewards + context.epochInfo.communityFund + context.epochInfo.operationalCost + return assertEq(allBudgets, context.epochInfo.stakingProceeds, BigInt(100)) +} + +export function verifyTotalWithdrawals(context: Context): VerificationResult { + const allocs = allocationsByUser(context) + + const claimed = Array.from(allocs.entries()) + .map(([user, donations]) => [user, donations.reduce((acc, donation) => acc + donation.amount, BigInt(0))] as const) + .map(([user, donationsSum]) => context.budgets.get(user)! - donationsSum) + .reduce((acc, user_claimed) => acc + user_claimed, BigInt(0)) + + const rewards = context.rewards + .filter((reward) => reward.matched !== BigInt(0)) + .reduce((acc, reward) => acc + reward.allocated + reward.matched, BigInt(0)) + + return assertEq(claimed + rewards, context.epochInfo.totalWithdrawals) +} diff --git a/epoch-verifier/src/_deprecated/epoch3/verifications/utils.ts b/epoch-verifier/src/_deprecated/epoch3/verifications/utils.ts new file mode 100644 index 0000000000..273fbea25f --- /dev/null +++ b/epoch-verifier/src/_deprecated/epoch3/verifications/utils.ts @@ -0,0 +1,54 @@ +import { VerificationResult } from "../runner"; + +type Predicate = (elem: T) => boolean | VerificationResult + +function abs(v: bigint): bigint { + if (v < BigInt(0)) { + return -v + } + return v +} + +export function assertEq(value: bigint, expected: bigint, maxMargin?: bigint, shouldPrintWhenValuesWithinExpectedError = false): VerificationResult { + let msg = "" + const margin = maxMargin ?? BigInt(0) + + const diff = abs(expected - value) + const isWithinMargin = diff <= margin + + const comparisonMsg = `Expected: ${expected}, got: ${value}, error: ${diff}` + if (!isWithinMargin) { + msg = `Comparison outside expected error margin. ${comparisonMsg}` + } + else if (shouldPrintWhenValuesWithinExpectedError) { + msg = comparisonMsg + } + + return { message: msg, result: isWithinMargin } + +} + +export function assertAll(elems: T[], predicate: Predicate): VerificationResult { + let isValid = true + let msg = "" + for (const elem of elems) { + const predicateResult = predicate(elem) + + if (typeof predicateResult === "boolean") { + if (!predicateResult) { + isValid = false + msg += `\n\tFailed at ${elem}` + } + } + else { + const { message: m, result: isSubpredicateValid } = predicateResult as VerificationResult + msg += `\n\t${elem}: ${m}` + if (!isSubpredicateValid) { + isValid = false + } + } + } + + return { message: msg, result: isValid } +} + diff --git a/epoch-verifier/src/_deprecated/epoch3/verifier.ts b/epoch-verifier/src/_deprecated/epoch3/verifier.ts new file mode 100644 index 0000000000..0a90a720c0 --- /dev/null +++ b/epoch-verifier/src/_deprecated/epoch3/verifier.ts @@ -0,0 +1,84 @@ +/* eslint-disable no-console */ +import { Command, Option } from "commander"; + +import { buildContext } from "./data/context"; +import { HttpFetcher } from "./data/fetcher"; +import { Runner } from "./runner"; +import { registerVerifications } from "./verifications"; + + +interface Options { + deployment?: string + epoch: number + url?: string +} + +const DEPLOYMENTS: { [key: string]: string } = { + mainnet: "https://backend.mainnet.octant.app", + master: "https://master-backend.octant.wildland.dev", + rc: "https://backend.mainnet.octant.wildland.dev", + testnet: "https://backend.testnet.octant.app", + uat: "https://uat-backend.octant.wildland.dev", +} + +const isNull = (obj: any) => obj === null + +function getDeploymentUrl(options: Options): string { + + if ((options.url !== undefined && options.deployment !== undefined) || (options.url === undefined && options.deployment === undefined)) { + console.error("Specify either custom url or deployment.") + process.exit(1) + } + + if (options.url !== undefined) { + return options.url! + } + + return DEPLOYMENTS[options.deployment!] + +} + +async function run(epoch: string, opts: any) { + const options: Options = { epoch: parseInt(epoch, 10), ...opts } + const baseUrl = getDeploymentUrl(options) + + console.log(`Using url: ${baseUrl}`) + console.log(`Running verification scripts for epoch: ${options.epoch}`) + + const fetcher = new HttpFetcher(baseUrl) + const results = await Promise.all([ + fetcher.apiGetUserBudgets(options.epoch), + fetcher.apiGetAllocations(options.epoch), + fetcher.apiGetRewards(options.epoch), + fetcher.apiGetEpochInfo(options.epoch) + ]) + + if (results.some(isNull)) { + process.exit(1) + } + + const [ + userBudgets, + allocations, + rewards, + epochInfo, + ] = results; + const context = buildContext(userBudgets!, allocations!, rewards!, epochInfo!) + + const runner = new Runner() + registerVerifications(runner) + const failures = await runner.run(context) + + process.exit(failures) + +} + + +const program = new Command(); +program + .description("Epoch verifier script.") + .addOption(new Option("--deployment ", "specify deployment to connect to").choices(Object.keys(DEPLOYMENTS))) + .option("--url ", "custom deployment url. Do not use with --deployment option") + .argument("", "Epoch number for which the verification should be done.") + .action(run) + .parse(process.argv); diff --git a/epoch-verifier/src/data/context.ts b/epoch-verifier/src/data/context.ts index 887653d911..c585c9954d 100644 --- a/epoch-verifier/src/data/context.ts +++ b/epoch-verifier/src/data/context.ts @@ -5,7 +5,8 @@ import { EpochInfo, Reward, UserBudget, - UserDonation + UserDonation, + EpochUqs } from "./models" export interface Context { @@ -13,6 +14,7 @@ export interface Context { budgets: Map epochInfo: EpochInfo rewards: Reward[] + uqs: Map } function transformToAllocations(allocationRecords: AllocationRecord[]): Allocation[] { @@ -20,14 +22,14 @@ function transformToAllocations(allocationRecords: AllocationRecord[]): Allocati for (const record of allocationRecords) { const prev = allocations.get(record.user) ?? { donations: [], user: record.user } - prev.donations.push({ amount: record.amount, proposal: record.proposal }) + prev.donations.push({ amount: record.amount, project: record.project }) allocations.set(record.user, prev) } return Array.from(allocations.values()) } -export function buildContext(userBudgets: UserBudget[], allocations: AllocationRecord[], rewards: Reward[], epochInfo: EpochInfo): Context { +export function buildContext(userBudgets: UserBudget[], allocations: AllocationRecord[], rewards: Reward[], epochInfo: EpochInfo, epochUqs: EpochUqs[]): Context { const positiveUserBudgets = userBudgets.filter(positiveUserBudget => positiveUserBudget.amount !== BigInt(0)); @@ -35,7 +37,8 @@ export function buildContext(userBudgets: UserBudget[], allocations: AllocationR allocations: transformToAllocations(allocations), budgets: new Map(positiveUserBudgets.map(value => [value.user, value.amount] as const)), epochInfo, - rewards + rewards, + epochUqs: new Map(epochUqs.map(value => [value.userAddress, value.uniquenessQuotient] as const)) } } @@ -43,21 +46,44 @@ export function allocationsByUser(context: Context): Map [alloc.user, alloc.donations] as const)) } -export function individualDonationsByProposals(context: Context): Map { +export function individualDonationsSummedByProjects(context: Context): Map { const individualDonations: Map = new Map() const donations: UserDonation[] = context.allocations.flatMap((alloc) => alloc.donations) - for (const donation of donations) { - const prev = individualDonations.get(donation.proposal) ?? BigInt(0) - individualDonations.set(donation.proposal, prev + donation.amount) + const prev = individualDonations.get(donation.project) ?? BigInt(0) + individualDonations.set(donation.project, prev + donation.amount) } return individualDonations } +export function individualDonationsAggregatedByProjectsWithUQs(context: Context): Map { + const individualDonations: Map = new Map() + const uqs = context.epochUqs; + + const donationsWithUsers = context.allocations.flatMap((alloc) => alloc.donations.map((donation) => ({ + ...donation, + user: alloc.user, + })) + ); + + for (const donation of donationsWithUsers) { + const prev = individualDonations.get(donation.project) ?? []; + individualDonations.set(donation.project, [ + ...prev, + { + amount: donation.amount, + project: donation.project, + uq: uqs.get(donation.user) ?? null, + }, + ]); + } + return individualDonations; +} + export function rewardsByProject(context: Context): Map { return new Map(context.rewards .filter(r => r.matched !== BigInt(0)) - .map((r) => [r.proposal, r] as const) + .map((r) => [r.project, r] as const) ); } diff --git a/epoch-verifier/src/data/fetcher.ts b/epoch-verifier/src/data/fetcher.ts index b263a4b54d..b637bde54b 100644 --- a/epoch-verifier/src/data/fetcher.ts +++ b/epoch-verifier/src/data/fetcher.ts @@ -12,6 +12,7 @@ import { UserBudget, AllocationRecord, ApiRewardsBudgets, ApiAllocations, ApiRewards, + ApiEpochUqs, EpochUqsImpl } from "./models"; const REQUEST_TIMEOUT = 150_000; @@ -19,16 +20,16 @@ const REQUEST_TIMEOUT = 150_000; export class HttpFetcher { readonly axios: Axios - constructor(baseUrl: string){ - this.axios = new Axios({baseURL: baseUrl, timeout: REQUEST_TIMEOUT}) + constructor(baseUrl: string) { + this.axios = new Axios({ baseURL: baseUrl, timeout: REQUEST_TIMEOUT }) } - async _get (url: string, resource: string, Factory: {new(): Deserializable}): Promise{ + async _get(url: string, resource: string, Factory: { new(): Deserializable }): Promise { const mapper = (data: any): T => new Factory().from(JSON.parse(data)) return this._do_get(url, resource, mapper) } - async _get_array (url: string, resource: string, Factory: {new(): Deserializable}, unwrapper?: (data: any) => any): Promise | null>{ + async _get_array(url: string, resource: string, Factory: { new(): Deserializable }, unwrapper?: (data: any) => any): Promise | null> { const dataUnwrapper = unwrapper ?? ((data: any) => data) @@ -36,10 +37,10 @@ export class HttpFetcher { return this._do_get(url, resource, mapper) } - async _do_get(url: string, resource: string, mapper: (data: any) => T): Promise{ + async _do_get(url: string, resource: string, mapper: (data: any) => T): Promise { return this.axios.get(url).then(response => { - if (response.status !== 200){ + if (response.status !== 200) { throw new Error(response.data) } console.log(`✅ Fetched ${resource}`) @@ -51,8 +52,7 @@ export class HttpFetcher { }) } - - async apiGetUserBudgets(epoch: number): Promise{ + async apiGetUserBudgets(epoch: number): Promise { return this._get_array(`/rewards/budgets/epoch/${epoch}`, "users' budgets", UserBudgetImpl, (data: ApiRewardsBudgets) => data.budgets) } @@ -60,11 +60,15 @@ export class HttpFetcher { return this._get_array(`/allocations/epoch/${epoch}?includeZeroAllocations=true`, "users' allocations", AllocationImpl, (data: ApiAllocations) => data.allocations) } - async apiGetRewards(epoch: number): Promise{ - return this._get_array(`/rewards/proposals/epoch/${epoch}`, "proposals rewards", RewardImpl, (data: ApiRewards) => data.rewards) + async apiGetRewards(epoch: number): Promise { + return this._get_array(`/rewards/projects/epoch/${epoch}`, "projects rewards", RewardImpl, (data: ApiRewards) => data.rewards) } async apiGetEpochInfo(epoch: number): Promise { return this._get(`/epochs/info/${epoch}`, "epoch info", EpochInfoImpl) } - } + + async apiGetEpochUqs(epoch: number): Promise { + return this._get_array(`/user/uq/${epoch}/all`, "epoch uqs", EpochUqsImpl, (data: ApiEpochUqs) => data.uqsInfo); + } +} diff --git a/epoch-verifier/src/data/models.ts b/epoch-verifier/src/data/models.ts index 31df7bb05c..c0da734b3c 100644 --- a/epoch-verifier/src/data/models.ts +++ b/epoch-verifier/src/data/models.ts @@ -17,7 +17,7 @@ export interface ApiAllocations { allocations: { amount: string; // wei donor: string; - proposal: string; + project: string; }[]; } @@ -28,6 +28,13 @@ export interface ApiRewards { }[]; } +export interface ApiEpochUqs { + uqsInfo: { + userAddress: string; + uniquenessQuotient: string; + }[]; +} + export interface UserBudget { amount: bigint; @@ -36,12 +43,13 @@ export interface UserBudget { export interface UserDonation { amount: bigint; - proposal: Address; + project: Address; + uq: number | null; } export interface AllocationRecord { amount: bigint; - proposal: Address; + project: Address; user: Address; } @@ -53,7 +61,7 @@ export interface Allocation { export interface Reward { allocated: bigint; matched: bigint; - proposal: Address; + project: Address; } export interface EpochInfo { @@ -70,6 +78,11 @@ export interface EpochInfo { totalWithdrawals: bigint; } +export interface EpochUqs { + userAddress: string; + uniquenessQuotient: number; +} + export class UserBudgetImpl implements Deserializable { user: Address; @@ -86,13 +99,13 @@ export class UserBudgetImpl implements Deserializable { export class AllocationImpl implements Deserializable { user: Address; - proposal: Address; + project: Address; amount: bigint; from(input: any) { this.user = input.donor - this.proposal = input.proposal; + this.project = input.project; this.amount = BigInt(input.amount ?? 0) return this @@ -104,10 +117,10 @@ export class RewardImpl implements Deserializable { matched: bigint; - proposal: Address; + project: Address; from(input: any) { - this.proposal = input.address + this.project = input.address this.allocated = BigInt(input.allocated) this.matched = BigInt(input.matched) @@ -140,13 +153,13 @@ export class EpochInfoImpl implements Deserializable { from(input: any) { - this.individualRewards = BigInt(input.individualRewards) + this.individualRewards = BigInt(input.vanillaIndividualRewards) this.matchedRewards = BigInt(input.matchedRewards) this.patronsRewards = BigInt(input.patronsRewards) this.stakingProceeds = BigInt(input.stakingProceeds) this.totalEffectiveDeposit = BigInt(input.totalEffectiveDeposit) this.totalRewards = BigInt(input.totalRewards) - this.totalWithdrawals = BigInt(input.totalWithdrawals) + this.totalWithdrawals = input.totalWithdrawals !== null ? BigInt(input.totalWithdrawals) : BigInt(0) this.operationalCost = BigInt(input.operationalCost) this.leftover = BigInt(input.leftover) this.ppf = BigInt(input.ppf) @@ -156,3 +169,14 @@ export class EpochInfoImpl implements Deserializable { } } +export class EpochUqsImpl implements Deserializable{ + userAddress: string; + uniquenessQuotient: number; + + from(input: any) { + this.userAddress = input.userAddress; + this.uniquenessQuotient = parseFloat(input.uniquenessQuotient); + return this; + } +} + diff --git a/epoch-verifier/src/runner.ts b/epoch-verifier/src/runner.ts index 2d0fafd3df..87204c3716 100644 --- a/epoch-verifier/src/runner.ts +++ b/epoch-verifier/src/runner.ts @@ -1,43 +1,43 @@ /* eslint-disable no-console */ import { Context } from "./data/context" -export type VerificationResult = {message: string, result: boolean} +export type VerificationResult = { message: string, result: boolean } type VerifyFunc = (context: Context) => VerificationResult; export interface Verification { readonly name: string - verify: VerifyFunc -} + verify: VerifyFunc +} export class Runner { - verifications: Verification[] + verifications: Verification[] - constructor(){ + constructor() { this.verifications = [] } - register(verification: Verification): void{ - this.verifications.push(verification) + register(verification: Verification): void { + this.verifications.push(verification) } async run(context: Context): Promise { console.log("Starting verifications ... ") - + const results = await Promise.all(this.verifications.map((v: Verification) => { const task = new Promise<[string, VerificationResult]>((resolve, _) => { console.log(`Verification ${v.name} started`) const result = v.verify(context) - if (result.result){ + if (result.result) { console.log(`Verification ${v.name} succeeded`) } resolve([v.name, result]) }) - const timeout = new Promise((_, reject) => { + const timeout = new Promise((_, reject) => { setTimeout(() => { reject(new Error(`Task ${v.name} timed out`)) }, 15_000) @@ -46,12 +46,12 @@ export class Runner { return Promise.race<[string, VerificationResult] | never>([task, timeout]) })) - + console.log("Run all verifications") const ok = results.filter(([_, result]) => result.result) - const failures = results.filter(([_, result]) => !result.result ) + const failures = results.filter(([_, result]) => !result.result) ok.forEach(([name, result]) => { console.log(`✅ ${name}: ${result.message}`) @@ -65,5 +65,5 @@ export class Runner { return failures.length } - + } diff --git a/epoch-verifier/src/verifications.ts b/epoch-verifier/src/verifications.ts index e510f04c01..b855684a91 100644 --- a/epoch-verifier/src/verifications.ts +++ b/epoch-verifier/src/verifications.ts @@ -2,7 +2,6 @@ import { Runner, Verification } from "./runner"; import { verifyBudgetsAreEqualToIndividualRewardsPlusHalfPpf, compareUserBudgetsVsTheirAllocations } from "./verifications/budgets"; import { verifyMatchedFunds, - verifyProjectsBelowThreshold, verifyTotalWithdrawals, verifyRewardsVsUserDonations, verifyUserDonationsVsRewards, @@ -13,15 +12,14 @@ import { export function registerVerifications(runner: Runner): void { const verifications: Verification[] = [ - {name: "User budgets vs allocations sums", verify: compareUserBudgetsVsTheirAllocations}, - {name: "User budgets vs half PPF", verify: verifyBudgetsAreEqualToIndividualRewardsPlusHalfPpf}, - {name: "Projects below threshold", verify: verifyProjectsBelowThreshold}, - {name: "Users allocated vs rewards allocated", verify: verifyUserDonationsVsRewards}, - {name: "Users rewards allocated vs allocated", verify: verifyRewardsVsUserDonations}, - {name: "Matched funds from allocations vs matched from rewards", verify: verifyMatchedFunds}, - {name: "Total withdrawals vs budgets, allocations, patrons and unclaimed", verify: verifyTotalWithdrawals}, - {name: "Matched funds from epoch info", verify: verifyMatchingFundFromEpochInfo}, - {name: "Total amounts from epoch info", verify: verifyAllEpochRewards}, + { name: "User budgets vs allocations sums", verify: compareUserBudgetsVsTheirAllocations }, + { name: "User budgets vs half PPF", verify: verifyBudgetsAreEqualToIndividualRewardsPlusHalfPpf }, + { name: "Matched funds from allocations with QF, UQ and capped vs matched from rewards", verify: verifyMatchedFunds }, + { name: "Users allocated vs rewards allocated", verify: verifyUserDonationsVsRewards }, + { name: "Users rewards allocated vs allocated", verify: verifyRewardsVsUserDonations }, + { name: "Total withdrawals vs budgets, allocations, patrons and unclaimed", verify: verifyTotalWithdrawals }, + { name: "Matched funds from epoch info", verify: verifyMatchingFundFromEpochInfo }, + { name: "Total amounts from epoch info", verify: verifyAllEpochRewards }, ] verifications.forEach((v) => runner.register(v)) diff --git a/epoch-verifier/src/verifications/donations.ts b/epoch-verifier/src/verifications/donations.ts index 28715a70e0..c1bafeb5c8 100644 --- a/epoch-verifier/src/verifications/donations.ts +++ b/epoch-verifier/src/verifications/donations.ts @@ -1,72 +1,111 @@ -import { assertAll, assertEq } from "./utils"; +import { sqrt, assertAll, assertEq, multiplyFloatByBigInt, SCALE_FACTOR } from "./utils"; -import { Context, allocationsByUser, individualDonationsByProposals, rewardsByProject } from "../data/context"; +import { Context, allocationsByUser, individualDonationsSummedByProjects, individualDonationsAggregatedByProjectsWithUQs, rewardsByProject } from "../data/context"; import { Address, Reward } from "../data/models"; import { VerificationResult } from "../runner"; -const PROPOSALS_NO = 30 +const PROPOSALS_NO = 30; -function getThreshold(individualAllocations: Map): bigint { - const allocationsSum = Array.from(individualAllocations.entries()).reduce((acc, [_, val]) => acc + val, BigInt(0)) - return allocationsSum / BigInt(PROPOSALS_NO) + +function _groupAllocationsByProjects(aggregatedDonations: Map): [Map, bigint] { + const result = new Map(); + let totalPlainQF = BigInt(0); + + aggregatedDonations.forEach((values, project) => { + const sumOfSqrt = values.reduce((sum, value) => sum + sqrt(multiplyFloatByBigInt(value.uq, value.amount)), BigInt(0)); + const resultValue = sumOfSqrt ** BigInt(2); + + totalPlainQF += resultValue; + + result.set(project, resultValue); + }); + + return [result, totalPlainQF]; } -function getUserAllocationsForProjectsAboveThreshold(context: Context): Map { - const individualDonations = individualDonationsByProposals(context) - const threshold = getThreshold(individualDonations) - const projectsAboveThreshold = Array.from(individualDonations.entries()).filter(([_, v]) => v > threshold) - return new Map(projectsAboveThreshold) +function _calculateMatchingFund(groupedAllocations: Map, totalAllocated: bigint, totalMatchedFunding: bigint): Map { + const result = new Map(); + groupedAllocations.forEach((value, address) => { + const matched = (value * totalMatchedFunding * BigInt(SCALE_FACTOR)) / totalAllocated / SCALE_FACTOR; + result.set(address, matched); + }); + + return result; } -export function verifyProjectsBelowThreshold(context: Context): VerificationResult { +function _applyCappedDistribution(aggregatedMatchedFunding: Map, totalMatchedFunding: bigint): [Map, bigint] { + const cappedMatchedFunding = (totalMatchedFunding * 20n) / 100n; // FUNDING_CAP_PERCENT = 0.2 + const cappedAggregatedMF = new Map(); + let leftover = BigInt(0); + + aggregatedMatchedFunding.forEach((value, address) => { + if (value > cappedMatchedFunding) { + cappedAggregatedMF.set(address, cappedMatchedFunding); + leftover += value - cappedMatchedFunding; + } else { + cappedAggregatedMF.set(address, value); + } + }); + + return [cappedAggregatedMF, leftover]; +} - const individualDonations = individualDonationsByProposals(context) - const threshold = getThreshold(individualDonations) +function _computeMatchingFundQFAndCapAndUQ(aggregatedDonations: Map, totalMatchedFunding: bigint): [Map, bigint] { + const [groupedAllocations, totalPlainQF] = _groupAllocationsByProjects(aggregatedDonations); - const projectsBelowThreshold = Array.from(individualDonations.entries()).filter(([_, v]) => v <= threshold).map(([p,_]) => p) - const rewards = rewardsByProject(context) + console.log("[INFO] Computed plainQF with donations:", groupedAllocations, ",", aggregatedDonations); + + const aggregatedMatchedFunding = _calculateMatchingFund(groupedAllocations, totalPlainQF, totalMatchedFunding); + + console.log("[INFO] Computed matchedFunding without cap:", aggregatedMatchedFunding); - return assertAll(projectsBelowThreshold, (project) => !rewards.has(project)) + const [cappedAggregatedMF, leftover] = _applyCappedDistribution(aggregatedMatchedFunding, totalMatchedFunding); + + console.log("[INFO] Computed matchedFunding with a cap:", cappedAggregatedMF); + + return [cappedAggregatedMF, leftover]; } -export function verifyUserDonationsVsRewards(context: Context): VerificationResult { - const projectsAboveThreshold = Array.from(getUserAllocationsForProjectsAboveThreshold(context).entries()) - const rewards = rewardsByProject(context) +function _getUserAllocationsForProjects(context: Context): Map { + const individualDonations = individualDonationsSummedByProjects(context); + return individualDonations; +} - return assertAll(projectsAboveThreshold, ([proposal, allocated]) => assertEq(allocated, rewards.get(proposal)!.allocated, BigInt(100), true)) +export function verifyUserDonationsVsRewards(context: Context): VerificationResult { + const projectsAllocations = Array.from(_getUserAllocationsForProjects(context).entries()); + const rewards = rewardsByProject(context); + return assertAll(projectsAllocations, ([proposal, allocated]) => assertEq(allocated, rewards.get(proposal)!.allocated, BigInt(100), true)); + return assertEq(10, 10, BigInt(100), true); } export function verifyRewardsVsUserDonations(context: Context): VerificationResult { - const projectsAboveThreshold = getUserAllocationsForProjectsAboveThreshold(context) - const rewards = Array.from(rewardsByProject(context).entries()) - - return assertAll(rewards, ([proposal, reward]: [Address, Reward]) => assertEq(reward.allocated, projectsAboveThreshold.get(proposal)!, BigInt(100), true)) + const projectsAllocations = _getUserAllocationsForProjects(context); + const rewards = Array.from(rewardsByProject(context).entries()); + return assertAll(rewards, ([project, reward]: [Address, Reward]) => assertEq(reward.allocated, projectsAllocations.get(project)!, BigInt(100), true)); + return assertEq(10, 10, BigInt(100), true); } export function verifyMatchedFunds(context: Context): VerificationResult { - const projectsAboveThreshold = Array.from(getUserAllocationsForProjectsAboveThreshold(context).entries()) const rewards = rewardsByProject(context) + const totalMatchedFunding = context.epochInfo.matchedRewards; + const aggregatedDonations = individualDonationsAggregatedByProjectsWithUQs(context) + const [aggregatedCappedMatchedFunding, leftover] = _computeMatchingFundQFAndCapAndUQ(aggregatedDonations, totalMatchedFunding); - const totalAllocations = projectsAboveThreshold.reduce((acc, [_, v]) => acc + v, BigInt(0)) - const matchingFund = context.epochInfo.matchedRewards - - return assertAll(projectsAboveThreshold, ([proposal, allocated]) => { - const matched = matchingFund * allocated / totalAllocations - return assertEq(matched, rewards.get(proposal)!.matched, BigInt(100), true) - }) - + return assertAll(aggregatedCappedMatchedFunding, ([project, matched]) => { + return assertEq(matched, rewards.get(project)!.matched, BigInt(100), true) + }); } export function verifyMatchingFundFromEpochInfo(context: Context): VerificationResult { - const verifyMatchedRewards = [context.epochInfo.totalRewards - context.epochInfo.ppf - context.epochInfo.individualRewards + context.epochInfo.patronsRewards, context.epochInfo.matchedRewards] + const verifyMatchedRewards = [context.epochInfo.totalRewards - context.epochInfo.ppf - context.epochInfo.individualRewards + context.epochInfo.patronsRewards, context.epochInfo.matchedRewards]; - return assertAll([verifyMatchedRewards], ([value, expected]) => assertEq(value, expected)) + return assertAll([verifyMatchedRewards], ([value, expected]) => assertEq(value, expected)); } export function verifyAllEpochRewards(context: Context): VerificationResult { - const allBudgets = context.epochInfo.individualRewards + context.epochInfo.ppf - context.epochInfo.patronsRewards + context.epochInfo.matchedRewards + context.epochInfo.communityFund + context.epochInfo.operationalCost - return assertEq(allBudgets, context.epochInfo.stakingProceeds, BigInt(100)) + const allBudgets = context.epochInfo.individualRewards + context.epochInfo.ppf - context.epochInfo.patronsRewards + context.epochInfo.matchedRewards + context.epochInfo.communityFund + context.epochInfo.operationalCost; + return assertEq(allBudgets, context.epochInfo.stakingProceeds, BigInt(100)); } export function verifyTotalWithdrawals(context: Context): VerificationResult { diff --git a/epoch-verifier/src/verifications/utils.ts b/epoch-verifier/src/verifications/utils.ts index 8c83fb84ee..49d23eb540 100644 --- a/epoch-verifier/src/verifications/utils.ts +++ b/epoch-verifier/src/verifications/utils.ts @@ -2,13 +2,37 @@ import { VerificationResult } from "../runner"; type Predicate = (elem: T) => boolean | VerificationResult +export const SCALE_FACTOR = 10n ** 18n; + function abs(v: bigint): bigint { - if (v < BigInt(0)){ + if (v < BigInt(0)) { return -v } return v } +export function sqrt(v: bigint): bigint { + // Heron's Algorithm + // SQRT by default has some error margin, i.e. for 1000 it returns 31 instead of 31.622776601683793319988935444327 + // This is due to the fact that we are using BigInts and not floating point numbers + // That's why we use SCALE_FACTOR coefficient to secure ourselves from such margin errors. + let x = v * SCALE_FACTOR; + let y = (x + 1n) / 2n; + + while (y < x) { + x = y; + y = (x + v * SCALE_FACTOR / x) / 2n; + } + + let result = x / (SCALE_FACTOR ** (1n / 2n)); + return result; +} + +export function multiplyFloatByBigInt(v1: number, v2: bigint): bigint { + let productBigInt = BigInt(v1 * 1e6) * v2; + return productBigInt / BigInt(1e6); +} + export function assertEq(value: bigint, expected: bigint, maxMargin?: bigint, shouldPrintWhenValuesWithinExpectedError = false): VerificationResult { let msg = "" const margin = maxMargin ?? BigInt(0) @@ -17,38 +41,38 @@ export function assertEq(value: bigint, expected: bigint, maxMargin?: bigint, sh const isWithinMargin = diff <= margin const comparisonMsg = `Expected: ${expected}, got: ${value}, error: ${diff}` - if (!isWithinMargin){ - msg = `Comparison outside expected error margin. ${comparisonMsg}` + if (!isWithinMargin) { + msg = `Comparison outside expected error margin. ${comparisonMsg}` } - else if (shouldPrintWhenValuesWithinExpectedError){ + else if (shouldPrintWhenValuesWithinExpectedError) { msg = comparisonMsg } - return {message: msg, result: isWithinMargin} - + return { message: msg, result: isWithinMargin } + } export function assertAll(elems: T[], predicate: Predicate): VerificationResult { let isValid = true let msg = "" - for (const elem of elems){ + for (const elem of elems) { const predicateResult = predicate(elem) - if ( typeof predicateResult === "boolean" ){ - if(!predicateResult){ + if (typeof predicateResult === "boolean") { + if (!predicateResult) { isValid = false msg += `\n\tFailed at ${elem}` } } else { - const { message: m, result: isSubpredicateValid} = predicateResult as VerificationResult - msg += `\n\t${elem}: ${m}` - if (!isSubpredicateValid){ + const { message: m, result: isSubpredicateValid } = predicateResult as VerificationResult + msg += `\n\t${elem}: ${m}` + if (!isSubpredicateValid) { isValid = false } } } - return {message: msg, result: isValid} + return { message: msg, result: isValid } } diff --git a/epoch-verifier/src/verifier.ts b/epoch-verifier/src/verifier.ts index f3eade0d6d..f47f9fe3b0 100644 --- a/epoch-verifier/src/verifier.ts +++ b/epoch-verifier/src/verifier.ts @@ -13,7 +13,7 @@ interface Options { url?: string } -const DEPLOYMENTS: {[key: string]: string} = { +const DEPLOYMENTS: { [key: string]: string } = { mainnet: "https://backend.mainnet.octant.app", master: "https://master-backend.octant.wildland.dev", rc: "https://backend.mainnet.octant.wildland.dev", @@ -39,7 +39,7 @@ function getDeploymentUrl(options: Options): string { } async function run(epoch: string, opts: any) { - const options: Options = {epoch: parseInt(epoch, 10), ...opts} + const options: Options = { epoch: parseInt(epoch, 10), ...opts } const baseUrl = getDeploymentUrl(options) console.log(`Using url: ${baseUrl}`) @@ -50,7 +50,8 @@ async function run(epoch: string, opts: any) { fetcher.apiGetUserBudgets(options.epoch), fetcher.apiGetAllocations(options.epoch), fetcher.apiGetRewards(options.epoch), - fetcher.apiGetEpochInfo(options.epoch) + fetcher.apiGetEpochInfo(options.epoch), + fetcher.apiGetEpochUqs(options.epoch) ]) if (results.some(isNull)) { @@ -62,8 +63,9 @@ async function run(epoch: string, opts: any) { allocations, rewards, epochInfo, + epochUqs ] = results; - const context = buildContext(userBudgets!, allocations!, rewards!, epochInfo!) + const context = buildContext(userBudgets!, allocations!, rewards!, epochInfo!, epochUqs!) const runner = new Runner() registerVerifications(runner) From 917c6dafd8a64e1793631e623f065da65d86c8fc Mon Sep 17 00:00:00 2001 From: Mateusz Stolecki Date: Fri, 19 Jul 2024 08:49:11 +0200 Subject: [PATCH 005/241] sanity tests for info endpoints Signed-off-by: Mateusz Stolecki --- backend/tests/api-e2e/test_api_info.py | 35 ++++++++++++++++++++++++++ backend/tests/conftest.py | 22 ++++++++++++---- package.json | 2 +- 3 files changed, 53 insertions(+), 6 deletions(-) create mode 100644 backend/tests/api-e2e/test_api_info.py diff --git a/backend/tests/api-e2e/test_api_info.py b/backend/tests/api-e2e/test_api_info.py new file mode 100644 index 0000000000..01bd44fcd2 --- /dev/null +++ b/backend/tests/api-e2e/test_api_info.py @@ -0,0 +1,35 @@ +import pytest + +from tests.conftest import Client +from tests.helpers.constants import STARTING_EPOCH + + +@pytest.mark.api +def test_info_basics( + client: Client, +): + # Check chain_info + chain_info, status_code = client.get_chain_info() + assert "chainName" in chain_info + assert "chainId" in chain_info + assert "smartContracts" in chain_info + assert status_code == 200 + + # Check healthcheck + healthcheck, status_code = client.get_healthcheck() + assert healthcheck["blockchain"] == "UP" + assert healthcheck["db"] == "UP" + assert healthcheck["subgraph"] == "UP" + assert status_code == 200 + + # Check version + version, status_code = client.get_version() + assert "id" in version + assert "env" in version + assert "chain" in version + assert status_code == 200 + + # Check sync_status + sync_status, status_code = client.sync_status() + assert sync_status["blockchainEpoch"] == STARTING_EPOCH + assert sync_status["indexedEpoch"] == STARTING_EPOCH diff --git a/backend/tests/conftest.py b/backend/tests/conftest.py index 10c211f099..b0f787979d 100644 --- a/backend/tests/conftest.py +++ b/backend/tests/conftest.py @@ -674,13 +674,25 @@ def __init__(self, flask_client: FlaskClient): def root(self): return self._flask_client.get("/").text - def sync_status(self): - rv = self._flask_client.get("/info/sync-status").text - return json.loads(rv) + def get_chain_info(self) -> tuple[dict, int]: + rv = self._flask_client.get("/info/chain-info") + return json.loads(rv.text), rv.status_code + + def get_healthcheck(self) -> tuple[dict, int]: + rv = self._flask_client.get("/info/healthcheck") + return json.loads(rv.text), rv.status_code + + def get_version(self) -> tuple[dict, int]: + rv = self._flask_client.get("/info/version") + return json.loads(rv.text), rv.status_code + + def sync_status(self) -> tuple[dict, int]: + rv = self._flask_client.get("/info/sync-status") + return json.loads(rv.text), rv.status_code def wait_for_sync(self, target): while True: - res = self.sync_status() + res, _ = self.sync_status() if res["indexedEpoch"] == res["blockchainEpoch"]: if res["indexedEpoch"] == target: return @@ -885,7 +897,7 @@ def client(flask_client: FlaskClient) -> Client: if i != 1: client.move_to_next_epoch(i) while True: - res = client.sync_status() + res, _ = client.sync_status() if res["indexedEpoch"] == res["blockchainEpoch"] and res["indexedEpoch"] > ( i - 1 ): diff --git a/package.json b/package.json index eef860c2dd..08bf136830 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "apitest:up": "docker compose -p apitest -f ./localenv/docker-compose.yaml -f ./localenv/apitest.yaml up anvil ipfs graph-postgres graph-node multideployer", "apitest:down": "docker compose -p apitest -f ./localenv/docker-compose.yaml -f ./localenv/apitest.yaml down", "apitest:run": "docker compose -p apitest -f ./localenv/docker-compose.yaml -f ./localenv/apitest.yaml run backend-apitest", - "preapitest:up": "docker rm -v -f $(docker ps -qa --filter 'name=apitest')", + "preapitest:up": "docker rm -v -f $(docker ps -qa --filter 'name=apitest') || true", "preapitest:run": "yarn localenv:build-backend" }, "repository": { From 21e6d9345fd4c2f8f762a4367672e858afe699f0 Mon Sep 17 00:00:00 2001 From: Mateusz Stolecki Date: Fri, 19 Jul 2024 09:04:03 +0200 Subject: [PATCH 006/241] fix readme command to run one api test Signed-off-by: Mateusz Stolecki --- backend/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/README.md b/backend/README.md index 6dc7112526..c1a5624a68 100644 --- a/backend/README.md +++ b/backend/README.md @@ -64,7 +64,7 @@ yarn apitest:run # in a second console When backend code is changed, just re-run `yarn apitest:run`. To run just one test, use standard pytest naming: ``` -yarn apitest:run tests/legacy/test_api_snapshot.py::test_pending_snapshot +yarn apitest:run tests/api-e2e/test_api_snapshot.py::test_pending_snapshot ``` To stop the env, run `yarn apitest:down` From ec629757115d85a020071b521d3c3b66e28e96c4 Mon Sep 17 00:00:00 2001 From: Mateusz Stolecki Date: Fri, 19 Jul 2024 09:21:22 +0200 Subject: [PATCH 007/241] one left out method not updated for sync status changes Signed-off-by: Mateusz Stolecki --- backend/tests/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/tests/conftest.py b/backend/tests/conftest.py index b0f787979d..467c30545f 100644 --- a/backend/tests/conftest.py +++ b/backend/tests/conftest.py @@ -700,7 +700,7 @@ def wait_for_sync(self, target): def wait_for_height_sync(self): while True: - res = self.sync_status() + res, _ = self.sync_status() if res["indexedHeight"] == res["blockchainHeight"]: return res["indexedHeight"] time.sleep(0.5) From bef3608448a1a1e4f0c83bcf42c6a19081491a38 Mon Sep 17 00:00:00 2001 From: Mateusz Stolecki Date: Fri, 19 Jul 2024 14:11:41 +0200 Subject: [PATCH 008/241] one more regression fix Signed-off-by: Mateusz Stolecki --- backend/tests/api-e2e/test_api_withdrawals.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/tests/api-e2e/test_api_withdrawals.py b/backend/tests/api-e2e/test_api_withdrawals.py index 978c98800a..4823cc3914 100644 --- a/backend/tests/api-e2e/test_api_withdrawals.py +++ b/backend/tests/api-e2e/test_api_withdrawals.py @@ -19,7 +19,7 @@ def test_withdrawals( ua_carol: UserAccount, setup_funds, ): - res = client.sync_status() + res, _ = client.sync_status() assert res["indexedEpoch"] == res["blockchainEpoch"] assert res["indexedEpoch"] > 0 From 23823b00f3813e7355804de9e625de17d52c26b7 Mon Sep 17 00:00:00 2001 From: Pawel Peregud Date: Fri, 26 Jul 2024 13:47:17 +0200 Subject: [PATCH 009/241] multideployer: better handling of errors multideployer: * non-zero exit on subcommand => http 500 * print stdout and stderr in logs * return stdout and stderr as a text accompanying the http 500 conftest: * if multideployer crashes, log the details --- backend/tests/conftest.py | 17 ++++++--- localenv/multideployer/server.py | 61 +++++++++++++++++++++++++------- 2 files changed, 61 insertions(+), 17 deletions(-) diff --git a/backend/tests/conftest.py b/backend/tests/conftest.py index 467c30545f..154f8df309 100644 --- a/backend/tests/conftest.py +++ b/backend/tests/conftest.py @@ -5,6 +5,7 @@ import os import time import urllib.request +import urllib.error from unittest.mock import MagicMock, Mock import gql @@ -530,10 +531,16 @@ def pytest_collection_modifyitems(config, items): def setup_deployment() -> dict[str, str]: deployer = os.getenv("CONTRACTS_DEPLOYER_URL") testname = f"octant_test_{random_string()}" - f = urllib.request.urlopen(f"{deployer}/?name={testname}") - deployment = f.read().decode().split("\n") - deployment = {var.split("=")[0]: var.split("=")[1] for var in deployment} - return deployment + try: + f = urllib.request.urlopen(f"{deployer}/?name={testname}") + deployment = f.read().decode().split("\n") + deployment = {var.split("=")[0]: var.split("=")[1] for var in deployment} + return deployment + except urllib.error.HTTPError as err: + current_app.logger.error(f"call to multideployer failed: {err}") + current_app.logger.error(f"multideployer failed: code is {err.code}") + current_app.logger.error(f"multideployer failed: msg is {err.msg}") + raise err def random_string() -> str: @@ -774,7 +781,7 @@ def get_locked_ratio_in_epoch(self, epoch: int): def get_rewards_budget(self, address: str, epoch: int): rv = self._flask_client.get(f"/rewards/budget/{address}/epoch/{epoch}").text - print( + current_app.logger.debug( "get_rewards_budget :", self._flask_client.get(f"/rewards/budget/{address}/epoch/{epoch}").request, ) diff --git a/localenv/multideployer/server.py b/localenv/multideployer/server.py index dd48d4a186..acbdbb9967 100644 --- a/localenv/multideployer/server.py +++ b/localenv/multideployer/server.py @@ -1,12 +1,17 @@ import json +import logging import os import subprocess +import sys from http.server import BaseHTTPRequestHandler from urllib.parse import parse_qsl, urlparse from http.server import HTTPServer from typing import Dict +logging.basicConfig(level=logging.DEBUG, stream=sys.stdout) +logger = logging.getLogger(__name__) + subgraph_admin_url = os.environ["SUBGRAPH_ADMIN_URL"] subgraph_query_url = os.environ["SUBGRAPH_QUERY_URL"] rpc_url = os.environ["RPC_URL"] @@ -18,6 +23,17 @@ deployments = {} +def report_failure(comp_p): + if comp_p.returncode == 0: + return + logging.debug( + f"Subprocess {comp_p.args} failed with returncode={comp_p.returncode}" + ) + logging.debug(f"STDOUT: {comp_p.stdout}") + logging.debug(f"STDERR: {comp_p.stdout}") + raise subprocess.CalledProcessError(comp_p.args, comp_p.returncode) + + def make_env(defs): os.environ.update(defs) return os.environ @@ -79,13 +95,21 @@ def run_sync(self, query): def get_env(self, name): if name in deployments: - print(f"Cached deployment of {name}") - return deployments[name] + logging.debug(f"Cached deployment of {name}") + return None, deployments[name] else: - print(f"New deployment of {name}") - new_deployment = self.new_deployment(name) - deployments[name] = new_deployment - return new_deployment + logging.debug(f"New deployment of {name}") + try: + new_deployment = self.new_deployment(name) + deployments[name] = new_deployment + return None, new_deployment + except subprocess.CalledProcessError as err: + logging.debug( + f"Subprocess {err.args} failed with returncode={err.returncode}" + ) + logging.debug(f"STDOUT: {err.stdout}") + logging.debug(f"STDERR: {err.stdout}") + return err, None def new_deployment(self, name) -> Dict[str, str]: contracts = subprocess.run( @@ -106,7 +130,9 @@ def new_deployment(self, name) -> Dict[str, str]: text=True, cwd="../hardhat/", ) + # TODO: this is fragile. Consider getting addresses from artifacts file instead. addrs = get_addresses(contracts.stdout.split("\n")) + logging.debug(f"got addresses: {addrs}") setup_subgraph(addrs, name) addrs["SUBGRAPH_NAME"] = name @@ -122,7 +148,7 @@ def query_data(self): def do_GET(self): if self.path == "/ping": self.send_response(200) - self.send_header('Content-type', "text/plain") + self.send_header("Content-type", "text/plain") self.end_headers() return @@ -131,15 +157,26 @@ def do_GET(self): self.send_response(400) self.wfile.write('Missing "name" field in GET query fields'.encode("utf-8")) return - results = self.get_env(query["name"]) + error, contracts = self.get_env(query["name"]) + if error is not None: + self.send_response(500) + self.send_header("Content-Type", "text/plain") + self.end_headers() + self.wfile.write( + f"Something went wrong, details below: \nerror: {error}\nSTDOUT: {error.stdout}\nSTDERR: {error.stderr}".encode() + ) + return + if ("Accept" in self.headers) and ( self.headers["Accept"] == "application/json" ): content_type = "application/json" - output = json.dumps(results).encode("utf-8") + output = json.dumps(contracts).encode("utf-8") else: content_type = "text/plain" - output = "\n".join([f"{k}={v}" for k, v in results.items()]).encode("utf-8") + output = "\n".join([f"{k}={v}" for k, v in contracts.items()]).encode( + "utf-8" + ) self.send_response(200) self.send_header("Content-Type", content_type) self.end_headers() @@ -149,8 +186,8 @@ def do_GET(self): if __name__ == "__main__": host = "0.0.0.0" port = 8022 - print(f"Multideployer listening on http://{host}:{port}") - print( + logging.debug(f"Multideployer listening on http://{host}:{port}") + logging.debug( f"Run GET with appropriate timeout value against http://{host}:{port}/?name=NAME_OF_YOUR_SUBGRAPH" ) server = HTTPServer((host, port), WebRequestHandler) From 48c02ea67d333e212a34b5ca05c7561520f7efba Mon Sep 17 00:00:00 2001 From: Pawel Peregud Date: Fri, 26 Jul 2024 16:20:31 +0200 Subject: [PATCH 010/241] bump hardhat version (& bump node for contracts) --- ci/Dockerfile.contracts-v1 | 2 +- contracts-v1/package.json | 5 +- contracts-v1/yarn.lock | 4106 +++++++++++++++--------------------- 3 files changed, 1763 insertions(+), 2350 deletions(-) diff --git a/ci/Dockerfile.contracts-v1 b/ci/Dockerfile.contracts-v1 index e9bca8497e..3da2b887b2 100644 --- a/ci/Dockerfile.contracts-v1 +++ b/ci/Dockerfile.contracts-v1 @@ -1,4 +1,4 @@ -FROM local-docker-registry.wildland.dev:80/library/node:16-alpine AS root +FROM local-docker-registry.wildland.dev:80/library/node:18-alpine AS root WORKDIR /app diff --git a/contracts-v1/package.json b/contracts-v1/package.json index d1963ed06e..1860816bbf 100644 --- a/contracts-v1/package.json +++ b/contracts-v1/package.json @@ -88,7 +88,7 @@ "eslint-plugin-typescript-sort-keys": "^2.1.0", "ethers": "^5.7.2", "fs-extra": "^10.1.0", - "hardhat": "^2.11.2", + "hardhat": "^2.14.2", "hardhat-deploy": "^0.11.15", "hardhat-deploy-ethers": "^0.3.0-beta.13", "hardhat-docgen": "^1.3.0", @@ -114,5 +114,8 @@ "yarn prettier:ts:pure", "eslint --fix" ] + }, + "dependencies": { + "ethereumjs-abi": "https://registry.npmjs.org/ethereumjs-abi/-/ethereumjs-abi-0.6.8.tgz" } } diff --git a/contracts-v1/yarn.lock b/contracts-v1/yarn.lock index 76f43c20a5..76e8c3181b 100644 --- a/contracts-v1/yarn.lock +++ b/contracts-v1/yarn.lock @@ -3,37 +3,39 @@ "@babel/code-frame@^7.0.0": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.18.6.tgz#3b25d38c89600baa2dcc219edfa88a74eb2c427a" - integrity sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q== + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.7.tgz#882fd9e09e8ee324e496bd040401c6f046ef4465" + integrity sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA== dependencies: - "@babel/highlight" "^7.18.6" + "@babel/highlight" "^7.24.7" + picocolors "^1.0.0" -"@babel/helper-validator-identifier@^7.18.6": - version "7.19.1" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2" - integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== +"@babel/helper-validator-identifier@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz#75b889cfaf9e35c2aaf42cf0d72c8e91719251db" + integrity sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w== -"@babel/highlight@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf" - integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g== +"@babel/highlight@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.24.7.tgz#a05ab1df134b286558aae0ed41e6c5f731bf409d" + integrity sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw== dependencies: - "@babel/helper-validator-identifier" "^7.18.6" - chalk "^2.0.0" + "@babel/helper-validator-identifier" "^7.24.7" + chalk "^2.4.2" js-tokens "^4.0.0" + picocolors "^1.0.0" -"@babel/parser@^7.18.4": - version "7.21.3" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.3.tgz#1d285d67a19162ff9daa358d4cb41d50c06220b3" - integrity sha512-lobG0d7aOfQRXh8AyklEAgZGvA4FShxo6xQbUrrT/cNBPUdIDojlokwJsQyCC/eKia7ifqM0yP+2DRZ4WKw2RQ== +"@babel/parser@^7.23.5": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.25.0.tgz#9fdc9237504d797b6e7b8f66e78ea7f570d256ad" + integrity sha512-CzdIU9jdP0dg7HdyB+bHvDJGagUv+qtzZt5rYCWwW6tITNqV9odjp6Qu41gkG0ca5UfdDUWrKkiAnHHdGRnOrA== -"@babel/runtime@^7.18.6", "@babel/runtime@^7.20.7": - version "7.21.0" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.21.0.tgz#5b55c9d394e5fcf304909a8b00c07dc217b56673" - integrity sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw== +"@babel/runtime@^7.18.6": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.25.0.tgz#3af9a91c1b739c569d5d80cc917280919c544ecb" + integrity sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw== dependencies: - regenerator-runtime "^0.13.11" + regenerator-runtime "^0.14.0" "@cspotcode/source-map-support@^0.8.0": version "0.8.1" @@ -43,13 +45,11 @@ "@jridgewell/trace-mapping" "0.3.9" "@defi-wonderland/smock@^2.3.4": - version "2.3.4" - resolved "https://registry.yarnpkg.com/@defi-wonderland/smock/-/smock-2.3.4.tgz#2bfe7e19052140634b25db344d77de9b0ac7a96b" - integrity sha512-VYJbsoCOdFRyGkAwvaQhQRrU6V8AjK3five8xdbo41DEE9n3qXzUNBUxyD9HhXB/dWWPFWT21IGw5Ztl6Qw3Ew== + version "2.4.0" + resolved "https://registry.yarnpkg.com/@defi-wonderland/smock/-/smock-2.4.0.tgz#a207ff0e6f2c9e7c723c12981d1b5d5bf05b5b54" + integrity sha512-eS5fuAa9MOVDvXsT7Qa4v9Tg0Pk5ypfY3JWyW93a5sqyY2E2nCuRRBC53IikM9z0tVB2YYA8C9bWK8Lc47mATw== dependencies: - "@nomicfoundation/ethereumjs-evm" "^1.0.0-rc.3" - "@nomicfoundation/ethereumjs-util" "^8.0.0-rc.3" - "@nomicfoundation/ethereumjs-vm" "^6.0.0-rc.3" + "@nomicfoundation/ethereumjs-util" "^9.0.4" diff "^5.0.0" lodash.isequal "^4.5.0" lodash.isequalwith "^4.4.0" @@ -63,19 +63,19 @@ dependencies: eslint-visitor-keys "^3.3.0" -"@eslint-community/regexpp@^4.4.0": - version "4.5.0" - resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.5.0.tgz#f6f729b02feee2c749f57e334b7a1b5f40a81724" - integrity sha512-vITaYzIcNmjn5tF5uxcZ/ft7/RXGrMUIS9HalWckEOF6ESiwXKoMzAQf2UW0aVd6rnOeExTJVd5hmWXucBKGXQ== +"@eslint-community/regexpp@^4.4.0", "@eslint-community/regexpp@^4.6.1": + version "4.11.0" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.11.0.tgz#b0ffd0312b4a3fd2d6f77237e7248a5ad3a680ae" + integrity sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A== -"@eslint/eslintrc@^2.0.2": - version "2.0.2" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.0.2.tgz#01575e38707add677cf73ca1589abba8da899a02" - integrity sha512-3W4f5tDUra+pA+FzgugqL2pRimUTDJWKr7BINqOpkZrC0uYI0NIc0/JFgBROCU07HR6GieA5m3/rsPIhDmCXTQ== +"@eslint/eslintrc@^2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad" + integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ== dependencies: ajv "^6.12.4" debug "^4.3.2" - espree "^9.5.1" + espree "^9.6.0" globals "^13.19.0" ignore "^5.2.0" import-fresh "^3.2.1" @@ -83,12 +83,26 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@eslint/js@8.37.0": - version "8.37.0" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.37.0.tgz#cf1b5fa24217fe007f6487a26d765274925efa7d" - integrity sha512-x5vzdtOOGgFVDCUs81QRB2+liax8rFg3+7hqM+QhBG0/G3F1ZsoYl97UrqgHgQ9KKT7G6c4V+aTUCgu/n22v1A== +"@eslint/js@8.57.0": + version "8.57.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.0.tgz#a5417ae8427873f1dd08b70b3574b453e67b5f7f" + integrity sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g== + +"@ethereumjs/rlp@^4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@ethereumjs/rlp/-/rlp-4.0.1.tgz#626fabfd9081baab3d0a3074b0c7ecaf674aaa41" + integrity sha512-tqsQiBQDQdmPWE1xkkBq4rlSW5QZpLOUJ5RJh2/9fug+q9tnUhuZoVLk7s0scUIKTOzEtR72DFBXI4WiZcMpvw== + +"@ethereumjs/util@^8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@ethereumjs/util/-/util-8.1.0.tgz#299df97fb6b034e0577ce9f94c7d9d1004409ed4" + integrity sha512-zQ0IqbdX8FZ9aw11vP+dZkKDkS+kgIvQPHnSAXzP9pLu+Rfu3D3XEeLbicvoXJTYnhZiPmsZUxgdzXwNKxRPbA== + dependencies: + "@ethereumjs/rlp" "^4.0.1" + ethereum-cryptography "^2.0.0" + micro-ftch "^0.3.1" -"@ethersproject/abi@5.7.0", "@ethersproject/abi@^5.0.0-beta.146", "@ethersproject/abi@^5.0.9", "@ethersproject/abi@^5.1.2", "@ethersproject/abi@^5.4.7", "@ethersproject/abi@^5.7.0": +"@ethersproject/abi@5.7.0", "@ethersproject/abi@^5.0.9", "@ethersproject/abi@^5.1.2", "@ethersproject/abi@^5.4.7", "@ethersproject/abi@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.7.0.tgz#b3f3e045bbbeed1af3947335c247ad625a44e449" integrity sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA== @@ -176,7 +190,7 @@ dependencies: "@ethersproject/bignumber" "^5.7.0" -"@ethersproject/contracts@5.7.0": +"@ethersproject/contracts@5.7.0", "@ethersproject/contracts@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/contracts/-/contracts-5.7.0.tgz#c305e775abd07e48aa590e1a877ed5c316f8bd1e" integrity sha512-5GJbzEU3X+d33CdfPhcyS+z8MzsTrBGk/sc+G+59+tPa9yFkl6HQ9D6L0QMgNTA9q8dT0XKxxkyp883XsQvbbg== @@ -279,7 +293,7 @@ dependencies: "@ethersproject/logger" "^5.7.0" -"@ethersproject/providers@5.7.2", "@ethersproject/providers@^5.4.7": +"@ethersproject/providers@5.7.2", "@ethersproject/providers@^5.4.7", "@ethersproject/providers@^5.7.2": version "5.7.2" resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.7.2.tgz#f8b1a4f275d7ce58cf0a2eec222269a08beb18cb" integrity sha512-g34EWZ1WWAVgr4aptGlVBF8mhl3VWjv+8hoAnzStu8Ah22VHBsuGzP17eb6xDVRzw895G4W7vvx60lFFur/1Rg== @@ -342,7 +356,7 @@ elliptic "6.5.4" hash.js "1.1.7" -"@ethersproject/solidity@5.7.0": +"@ethersproject/solidity@5.7.0", "@ethersproject/solidity@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/solidity/-/solidity-5.7.0.tgz#5e9c911d8a2acce2a5ebb48a5e2e0af20b631cb8" integrity sha512-HmabMd2Dt/raavyaGukF4XxizWKhKQ24DoLtdNbBmNKUOPqwjsKQSdV9GQtj9CBEea9DlzETlVER1gYeXXBGaA== @@ -387,7 +401,7 @@ "@ethersproject/constants" "^5.7.0" "@ethersproject/logger" "^5.7.0" -"@ethersproject/wallet@5.7.0": +"@ethersproject/wallet@5.7.0", "@ethersproject/wallet@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/wallet/-/wallet-5.7.0.tgz#4e5d0790d96fe21d61d38fb40324e6c7ef350b2d" integrity sha512-MhmXlJXEJFBFVKrDLB4ZdDzxcBxQ3rLyCkhNqVu3CDYvR97E+8r01UgrI+TI99Le+aYm/in/0vp86guJuM7FCA== @@ -430,13 +444,18 @@ "@ethersproject/properties" "^5.7.0" "@ethersproject/strings" "^5.7.0" -"@humanwhocodes/config-array@^0.11.8": - version "0.11.8" - resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.8.tgz#03595ac2075a4dc0f191cc2131de14fbd7d410b9" - integrity sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g== +"@fastify/busboy@^2.0.0": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-2.1.1.tgz#b9da6a878a371829a0502c9b6c1c143ef6663f4d" + integrity sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA== + +"@humanwhocodes/config-array@^0.11.14": + version "0.11.14" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.14.tgz#d78e481a039f7566ecc9660b4ea7fe6b1fec442b" + integrity sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg== dependencies: - "@humanwhocodes/object-schema" "^1.2.1" - debug "^4.1.1" + "@humanwhocodes/object-schema" "^2.0.2" + debug "^4.3.1" minimatch "^3.0.5" "@humanwhocodes/module-importer@^1.0.1": @@ -444,42 +463,42 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== -"@humanwhocodes/object-schema@^1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" - integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== +"@humanwhocodes/object-schema@^2.0.2": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3" + integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== -"@jridgewell/gen-mapping@^0.3.0": - version "0.3.2" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9" - integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A== +"@jridgewell/gen-mapping@^0.3.5": + version "0.3.5" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz#dcce6aff74bdf6dad1a95802b69b04a2fcb1fb36" + integrity sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg== dependencies: - "@jridgewell/set-array" "^1.0.1" + "@jridgewell/set-array" "^1.2.1" "@jridgewell/sourcemap-codec" "^1.4.10" - "@jridgewell/trace-mapping" "^0.3.9" + "@jridgewell/trace-mapping" "^0.3.24" -"@jridgewell/resolve-uri@3.1.0", "@jridgewell/resolve-uri@^3.0.3": - version "3.1.0" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" - integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== +"@jridgewell/resolve-uri@^3.0.3", "@jridgewell/resolve-uri@^3.1.0": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== -"@jridgewell/set-array@^1.0.1": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" - integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== +"@jridgewell/set-array@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" + integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== -"@jridgewell/source-map@^0.3.2": - version "0.3.2" - resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.2.tgz#f45351aaed4527a298512ec72f81040c998580fb" - integrity sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw== +"@jridgewell/source-map@^0.3.3": + version "0.3.6" + resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.6.tgz#9d71ca886e32502eb9362c9a74a46787c36df81a" + integrity sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ== dependencies: - "@jridgewell/gen-mapping" "^0.3.0" - "@jridgewell/trace-mapping" "^0.3.9" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" -"@jridgewell/sourcemap-codec@1.4.14", "@jridgewell/sourcemap-codec@^1.4.10": - version "1.4.14" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" - integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" + integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== "@jridgewell/trace-mapping@0.3.9": version "0.3.9" @@ -489,13 +508,13 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" -"@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.9": - version "0.3.17" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz#793041277af9073b0951a7fe0f0d8c4c98c36985" - integrity sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g== +"@jridgewell/trace-mapping@^0.3.20", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": + version "0.3.25" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" + integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== dependencies: - "@jridgewell/resolve-uri" "3.1.0" - "@jridgewell/sourcemap-codec" "1.4.14" + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" "@metamask/eth-sig-util@^4.0.0": version "4.0.1" @@ -508,19 +527,23 @@ tweetnacl "^1.0.3" tweetnacl-util "^0.15.1" -"@morgan-stanley/ts-mocking-bird@^0.6.2": - version "0.6.4" - resolved "https://registry.yarnpkg.com/@morgan-stanley/ts-mocking-bird/-/ts-mocking-bird-0.6.4.tgz#2e4b60d42957bab3b50b67dbf14c3da2f62a39f7" - integrity sha512-57VJIflP8eR2xXa9cD1LUawh+Gh+BVQfVu0n6GALyg/AqV/Nz25kDRvws3i9kIe1PTrbsZZOYpsYp6bXPd6nVA== +"@noble/curves@1.4.2", "@noble/curves@~1.4.0": + version "1.4.2" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.4.2.tgz#40309198c76ed71bc6dbf7ba24e81ceb4d0d1fe9" + integrity sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw== dependencies: - lodash "^4.17.16" - uuid "^7.0.3" + "@noble/hashes" "1.4.0" "@noble/hashes@1.2.0", "@noble/hashes@~1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.2.0.tgz#a3150eeb09cc7ab207ebf6d7b9ad311a9bdbed12" integrity sha512-FZfhjEDbT5GRswV3C6uvLPHMiVD6lQBmpoX5+eSiPaMTXte/IKqI5dykDxzZB/WBeK/CDuQRBWarPdi3FNY2zQ== +"@noble/hashes@1.4.0", "@noble/hashes@^1.4.0", "@noble/hashes@~1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.4.0.tgz#45814aa329f30e4fe0ba49426f49dfccdd066426" + integrity sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg== + "@noble/secp256k1@1.7.1", "@noble/secp256k1@~1.7.0": version "1.7.1" resolved "https://registry.yarnpkg.com/@noble/secp256k1/-/secp256k1-1.7.1.tgz#b251c70f824ce3ca7f8dc3df08d58f005cc0507c" @@ -547,137 +570,83 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@nomicfoundation/ethereumjs-block@4.2.2", "@nomicfoundation/ethereumjs-block@^4.0.0": - version "4.2.2" - resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-block/-/ethereumjs-block-4.2.2.tgz#f317078c810a54381c682d0c12e1e81acfc11599" - integrity sha512-atjpt4gc6ZGZUPHBAQaUJsm1l/VCo7FmyQ780tMGO8QStjLdhz09dXynmhwVTy5YbRr0FOh/uX3QaEM0yIB2Zg== - dependencies: - "@nomicfoundation/ethereumjs-common" "3.1.2" - "@nomicfoundation/ethereumjs-rlp" "4.0.3" - "@nomicfoundation/ethereumjs-trie" "5.0.5" - "@nomicfoundation/ethereumjs-tx" "4.1.2" - "@nomicfoundation/ethereumjs-util" "8.0.6" - ethereum-cryptography "0.1.3" - -"@nomicfoundation/ethereumjs-blockchain@6.2.2", "@nomicfoundation/ethereumjs-blockchain@^6.0.0": - version "6.2.2" - resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-blockchain/-/ethereumjs-blockchain-6.2.2.tgz#9f79dd2b3dc73f5d5a220f7d8a734330c4c26320" - integrity sha512-6AIB2MoTEPZJLl6IRKcbd8mUmaBAQ/NMe3O7OsAOIiDjMNPPH5KaUQiLfbVlegT4wKIg/GOsFH7XlH2KDVoJNg== - dependencies: - "@nomicfoundation/ethereumjs-block" "4.2.2" - "@nomicfoundation/ethereumjs-common" "3.1.2" - "@nomicfoundation/ethereumjs-ethash" "2.0.5" - "@nomicfoundation/ethereumjs-rlp" "4.0.3" - "@nomicfoundation/ethereumjs-trie" "5.0.5" - "@nomicfoundation/ethereumjs-util" "8.0.6" - abstract-level "^1.0.3" - debug "^4.3.3" - ethereum-cryptography "0.1.3" - level "^8.0.0" - lru-cache "^5.1.1" - memory-level "^1.0.0" +"@nomicfoundation/edr-darwin-arm64@0.4.2": + version "0.4.2" + resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-darwin-arm64/-/edr-darwin-arm64-0.4.2.tgz#2ff98535f272c9f2a7d06eeda93fe7b207a348a4" + integrity sha512-S+hhepupfqpBvMa9M1PVS08sVjGXsLnjyAsjhrrsjsNuTHVLhKzhkguvBD5g4If5skrwgOaVqpag4wnQbd15kQ== -"@nomicfoundation/ethereumjs-common@3.1.2", "@nomicfoundation/ethereumjs-common@^3.0.0": - version "3.1.2" - resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-common/-/ethereumjs-common-3.1.2.tgz#041086da66ed40f2bf2a2116a1f2f0fcf33fb80d" - integrity sha512-JAEBpIua62dyObHM9KI2b4wHZcRQYYge9gxiygTWa3lNCr2zo+K0TbypDpgiNij5MCGNWP1eboNfNfx1a3vkvA== - dependencies: - "@nomicfoundation/ethereumjs-util" "8.0.6" - crc-32 "^1.2.0" +"@nomicfoundation/edr-darwin-x64@0.4.2": + version "0.4.2" + resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-darwin-x64/-/edr-darwin-x64-0.4.2.tgz#001dcd0e7fa4c52046d283b0dc61e63a60c614dd" + integrity sha512-/zM94AUrXz6CmcsecRNHJ50jABDUFafmGc4iBmkfX/mTp4tVZj7XTyIogrQIt0FnTaeb4CgZoLap2+8tW/Uldg== -"@nomicfoundation/ethereumjs-ethash@2.0.5": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-ethash/-/ethereumjs-ethash-2.0.5.tgz#0c605812f6f4589a9f6d597db537bbf3b86469db" - integrity sha512-xlLdcICGgAYyYmnI3r1t0R5fKGBJNDQSOQxXNjVO99JmxJIdXR5MgPo5CSJO1RpyzKOgzi3uIFn8agv564dZEQ== - dependencies: - "@nomicfoundation/ethereumjs-block" "4.2.2" - "@nomicfoundation/ethereumjs-rlp" "4.0.3" - "@nomicfoundation/ethereumjs-util" "8.0.6" - abstract-level "^1.0.3" - bigint-crypto-utils "^3.0.23" - ethereum-cryptography "0.1.3" +"@nomicfoundation/edr-linux-arm64-gnu@0.4.2": + version "0.4.2" + resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-linux-arm64-gnu/-/edr-linux-arm64-gnu-0.4.2.tgz#6d19f8265c8ffb22e29bc5bbbb5d1913fe4b306b" + integrity sha512-TV3Pr2tFvvmCfPCi9PaCGLtqn+oLaPKfL2NWpnoCeFFdzDQXi2L930yP1oUPY5RXd78NLdVHMkEkbhb2b6Wuvg== -"@nomicfoundation/ethereumjs-evm@1.3.2", "@nomicfoundation/ethereumjs-evm@^1.0.0", "@nomicfoundation/ethereumjs-evm@^1.0.0-rc.3": - version "1.3.2" - resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-evm/-/ethereumjs-evm-1.3.2.tgz#f9d6bafd5c23d07ab75b8649d589af1a43b60bfc" - integrity sha512-I00d4MwXuobyoqdPe/12dxUQxTYzX8OckSaWsMcWAfQhgVDvBx6ffPyP/w1aL0NW7MjyerySPcSVfDJAMHjilw== - dependencies: - "@nomicfoundation/ethereumjs-common" "3.1.2" - "@nomicfoundation/ethereumjs-util" "8.0.6" - "@types/async-eventemitter" "^0.2.1" - async-eventemitter "^0.2.4" - debug "^4.3.3" - ethereum-cryptography "0.1.3" - mcl-wasm "^0.7.1" - rustbn.js "~0.2.0" +"@nomicfoundation/edr-linux-arm64-musl@0.4.2": + version "0.4.2" + resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-linux-arm64-musl/-/edr-linux-arm64-musl-0.4.2.tgz#0b01aa405fdc8048c7a8e95c737f29b437536a30" + integrity sha512-PALwrLBk1M9rolXyhSX8xdhe5jL0qf/PgiCIF7W7lUyVKrI/I0oiU0EHDk/Xw7yi2UJg4WRyhhZoHYa0g4g8Qg== -"@nomicfoundation/ethereumjs-rlp@4.0.3", "@nomicfoundation/ethereumjs-rlp@^4.0.0": - version "4.0.3" - resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-rlp/-/ethereumjs-rlp-4.0.3.tgz#8d9147fbd0d49e8f4c5ce729d226694a8fe03eb8" - integrity sha512-DZMzB/lqPK78T6MluyXqtlRmOMcsZbTTbbEyAjo0ncaff2mqu/k8a79PBcyvpgAhWD/R59Fjq/x3ro5Lof0AtA== +"@nomicfoundation/edr-linux-x64-gnu@0.4.2": + version "0.4.2" + resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-linux-x64-gnu/-/edr-linux-x64-gnu-0.4.2.tgz#10959fd4db9b333d3e0559cb893e109611889af0" + integrity sha512-5svkftypDjAZ1LxV1onojlaqPRxrTEjJLkrUwLL+Fao5ZMe7aTnk5QQ1Jv76gW6WYZnMXNgjPhRcnw3oSNrqFA== -"@nomicfoundation/ethereumjs-statemanager@1.0.5", "@nomicfoundation/ethereumjs-statemanager@^1.0.0": - version "1.0.5" - resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-statemanager/-/ethereumjs-statemanager-1.0.5.tgz#951cc9ff2c421d40233d2e9d0fe033db2391ee44" - integrity sha512-CAhzpzTR5toh/qOJIZUUOnWekUXuRqkkzaGAQrVcF457VhtCmr+ddZjjK50KNZ524c1XP8cISguEVNqJ6ij1sA== - dependencies: - "@nomicfoundation/ethereumjs-common" "3.1.2" - "@nomicfoundation/ethereumjs-rlp" "4.0.3" - "@nomicfoundation/ethereumjs-trie" "5.0.5" - "@nomicfoundation/ethereumjs-util" "8.0.6" - debug "^4.3.3" - ethereum-cryptography "0.1.3" - functional-red-black-tree "^1.0.1" +"@nomicfoundation/edr-linux-x64-musl@0.4.2": + version "0.4.2" + resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-linux-x64-musl/-/edr-linux-x64-musl-0.4.2.tgz#8de64a2dfd869dad930dd0eb9572a0593d382379" + integrity sha512-qiMlXQTggdH9zfOB4Eil4rQ95z8s7QdLJcOfz5Aym12qJNkCyF9hi4cc4dDCWA0CdI3x3oLbuf8qb81SF8R45w== -"@nomicfoundation/ethereumjs-trie@5.0.5", "@nomicfoundation/ethereumjs-trie@^5.0.0": - version "5.0.5" - resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-trie/-/ethereumjs-trie-5.0.5.tgz#bf31c9306dcbba2007fad668e96109ddb147040c" - integrity sha512-+8sNZrXkzvA1NH5F4kz5RSYl1I6iaRz7mAZRsyxOm0IVY4UaP43Ofvfp/TwOalFunurQrYB5pRO40+8FBcxFMA== - dependencies: - "@nomicfoundation/ethereumjs-rlp" "4.0.3" - "@nomicfoundation/ethereumjs-util" "8.0.6" - ethereum-cryptography "0.1.3" - readable-stream "^3.6.0" +"@nomicfoundation/edr-win32-x64-msvc@0.4.2": + version "0.4.2" + resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-win32-x64-msvc/-/edr-win32-x64-msvc-0.4.2.tgz#13ad4bab9fd68853930e1a3d87c78d69d1d0e2ef" + integrity sha512-hDkAb0iaMmGYwBY/rA1oCX8VpsezfQcHPEPIEGXEcWC3WbnOgIZo0Qkpu/g0OMtFOJSQlWLXvKZuV7blhnrQag== -"@nomicfoundation/ethereumjs-tx@4.1.2", "@nomicfoundation/ethereumjs-tx@^4.0.0": - version "4.1.2" - resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-tx/-/ethereumjs-tx-4.1.2.tgz#8659fad7f9094b7eb82aa6cc3c8097cb1c42ff31" - integrity sha512-emJBJZpmTdUa09cqxQqHaysbBI9Od353ZazeH7WgPb35miMgNY6mb7/3vBA98N5lUW/rgkiItjX0KZfIzihSoQ== - dependencies: - "@nomicfoundation/ethereumjs-common" "3.1.2" - "@nomicfoundation/ethereumjs-rlp" "4.0.3" - "@nomicfoundation/ethereumjs-util" "8.0.6" +"@nomicfoundation/edr@^0.4.1": + version "0.4.2" + resolved "https://registry.yarnpkg.com/@nomicfoundation/edr/-/edr-0.4.2.tgz#9d7550182d4f75d7510e265ebd3474c4f6fcb62a" + integrity sha512-U7v0HuZHfrsl/5FpUzuB2FYA0+FUglHHwiO6NhvLtNYKMZcPzdS6iUriMp/7GWs0SVxW3bAht9GinZPxdhVwWg== + dependencies: + "@nomicfoundation/edr-darwin-arm64" "0.4.2" + "@nomicfoundation/edr-darwin-x64" "0.4.2" + "@nomicfoundation/edr-linux-arm64-gnu" "0.4.2" + "@nomicfoundation/edr-linux-arm64-musl" "0.4.2" + "@nomicfoundation/edr-linux-x64-gnu" "0.4.2" + "@nomicfoundation/edr-linux-x64-musl" "0.4.2" + "@nomicfoundation/edr-win32-x64-msvc" "0.4.2" + +"@nomicfoundation/ethereumjs-common@4.0.4": + version "4.0.4" + resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-common/-/ethereumjs-common-4.0.4.tgz#9901f513af2d4802da87c66d6f255b510bef5acb" + integrity sha512-9Rgb658lcWsjiicr5GzNCjI1llow/7r0k50dLL95OJ+6iZJcVbi15r3Y0xh2cIO+zgX0WIHcbzIu6FeQf9KPrg== + dependencies: + "@nomicfoundation/ethereumjs-util" "9.0.4" + +"@nomicfoundation/ethereumjs-rlp@5.0.4": + version "5.0.4" + resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-rlp/-/ethereumjs-rlp-5.0.4.tgz#66c95256fc3c909f6fb18f6a586475fc9762fa30" + integrity sha512-8H1S3s8F6QueOc/X92SdrA4RDenpiAEqMg5vJH99kcQaCy/a3Q6fgseo75mgWlbanGJXSlAPtnCeG9jvfTYXlw== + +"@nomicfoundation/ethereumjs-tx@5.0.4": + version "5.0.4" + resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-tx/-/ethereumjs-tx-5.0.4.tgz#b0ceb58c98cc34367d40a30d255d6315b2f456da" + integrity sha512-Xjv8wAKJGMrP1f0n2PeyfFCCojHd7iS3s/Ab7qzF1S64kxZ8Z22LCMynArYsVqiFx6rzYy548HNVEyI+AYN/kw== + dependencies: + "@nomicfoundation/ethereumjs-common" "4.0.4" + "@nomicfoundation/ethereumjs-rlp" "5.0.4" + "@nomicfoundation/ethereumjs-util" "9.0.4" ethereum-cryptography "0.1.3" -"@nomicfoundation/ethereumjs-util@8.0.6", "@nomicfoundation/ethereumjs-util@^8.0.0", "@nomicfoundation/ethereumjs-util@^8.0.0-rc.3": - version "8.0.6" - resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-util/-/ethereumjs-util-8.0.6.tgz#dbce5d258b017b37aa58b3a7c330ad59d10ccf0b" - integrity sha512-jOQfF44laa7xRfbfLXojdlcpkvxeHrE2Xu7tSeITsWFgoII163MzjOwFEzSNozHYieFysyoEMhCdP+NY5ikstw== +"@nomicfoundation/ethereumjs-util@9.0.4", "@nomicfoundation/ethereumjs-util@^9.0.4": + version "9.0.4" + resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-util/-/ethereumjs-util-9.0.4.tgz#84c5274e82018b154244c877b76bc049a4ed7b38" + integrity sha512-sLOzjnSrlx9Bb9EFNtHzK/FJFsfg2re6bsGqinFinH1gCqVfz9YYlXiMWwDM4C/L4ywuHFCYwfKTVr/QHQcU0Q== dependencies: - "@nomicfoundation/ethereumjs-rlp" "4.0.3" - ethereum-cryptography "0.1.3" - -"@nomicfoundation/ethereumjs-vm@^6.0.0", "@nomicfoundation/ethereumjs-vm@^6.0.0-rc.3": - version "6.4.2" - resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-vm/-/ethereumjs-vm-6.4.2.tgz#af1cf62e6c0054bc2b7febc8556d032433d1b18c" - integrity sha512-PRTyxZMP6kx+OdAzBhuH1LD2Yw+hrSpaytftvaK//thDy2OI07S0nrTdbrdk7b8ZVPAc9H9oTwFBl3/wJ3w15g== - dependencies: - "@nomicfoundation/ethereumjs-block" "4.2.2" - "@nomicfoundation/ethereumjs-blockchain" "6.2.2" - "@nomicfoundation/ethereumjs-common" "3.1.2" - "@nomicfoundation/ethereumjs-evm" "1.3.2" - "@nomicfoundation/ethereumjs-rlp" "4.0.3" - "@nomicfoundation/ethereumjs-statemanager" "1.0.5" - "@nomicfoundation/ethereumjs-trie" "5.0.5" - "@nomicfoundation/ethereumjs-tx" "4.1.2" - "@nomicfoundation/ethereumjs-util" "8.0.6" - "@types/async-eventemitter" "^0.2.1" - async-eventemitter "^0.2.4" - debug "^4.3.3" + "@nomicfoundation/ethereumjs-rlp" "5.0.4" ethereum-cryptography "0.1.3" - functional-red-black-tree "^1.0.1" - mcl-wasm "^0.7.1" - rustbn.js "~0.2.0" "@nomicfoundation/hardhat-chai-matchers@^1.0.5": version "1.0.6" @@ -690,71 +659,53 @@ deep-eql "^4.0.1" ordinal "^1.0.3" -"@nomicfoundation/solidity-analyzer-darwin-arm64@0.1.1": - version "0.1.1" - resolved "https://registry.yarnpkg.com/@nomicfoundation/solidity-analyzer-darwin-arm64/-/solidity-analyzer-darwin-arm64-0.1.1.tgz#4c858096b1c17fe58a474fe81b46815f93645c15" - integrity sha512-KcTodaQw8ivDZyF+D76FokN/HdpgGpfjc/gFCImdLUyqB6eSWVaZPazMbeAjmfhx3R0zm/NYVzxwAokFKgrc0w== - -"@nomicfoundation/solidity-analyzer-darwin-x64@0.1.1": - version "0.1.1" - resolved "https://registry.yarnpkg.com/@nomicfoundation/solidity-analyzer-darwin-x64/-/solidity-analyzer-darwin-x64-0.1.1.tgz#6e25ccdf6e2d22389c35553b64fe6f3fdaec432c" - integrity sha512-XhQG4BaJE6cIbjAVtzGOGbK3sn1BO9W29uhk9J8y8fZF1DYz0Doj8QDMfpMu+A6TjPDs61lbsmeYodIDnfveSA== - -"@nomicfoundation/solidity-analyzer-freebsd-x64@0.1.1": - version "0.1.1" - resolved "https://registry.yarnpkg.com/@nomicfoundation/solidity-analyzer-freebsd-x64/-/solidity-analyzer-freebsd-x64-0.1.1.tgz#0a224ea50317139caeebcdedd435c28a039d169c" - integrity sha512-GHF1VKRdHW3G8CndkwdaeLkVBi5A9u2jwtlS7SLhBc8b5U/GcoL39Q+1CSO3hYqePNP+eV5YI7Zgm0ea6kMHoA== - -"@nomicfoundation/solidity-analyzer-linux-arm64-gnu@0.1.1": - version "0.1.1" - resolved "https://registry.yarnpkg.com/@nomicfoundation/solidity-analyzer-linux-arm64-gnu/-/solidity-analyzer-linux-arm64-gnu-0.1.1.tgz#dfa085d9ffab9efb2e7b383aed3f557f7687ac2b" - integrity sha512-g4Cv2fO37ZsUENQ2vwPnZc2zRenHyAxHcyBjKcjaSmmkKrFr64yvzeNO8S3GBFCo90rfochLs99wFVGT/0owpg== - -"@nomicfoundation/solidity-analyzer-linux-arm64-musl@0.1.1": - version "0.1.1" - resolved "https://registry.yarnpkg.com/@nomicfoundation/solidity-analyzer-linux-arm64-musl/-/solidity-analyzer-linux-arm64-musl-0.1.1.tgz#c9e06b5d513dd3ab02a7ac069c160051675889a4" - integrity sha512-WJ3CE5Oek25OGE3WwzK7oaopY8xMw9Lhb0mlYuJl/maZVo+WtP36XoQTb7bW/i8aAdHW5Z+BqrHMux23pvxG3w== - -"@nomicfoundation/solidity-analyzer-linux-x64-gnu@0.1.1": - version "0.1.1" - resolved "https://registry.yarnpkg.com/@nomicfoundation/solidity-analyzer-linux-x64-gnu/-/solidity-analyzer-linux-x64-gnu-0.1.1.tgz#8d328d16839e52571f72f2998c81e46bf320f893" - integrity sha512-5WN7leSr5fkUBBjE4f3wKENUy9HQStu7HmWqbtknfXkkil+eNWiBV275IOlpXku7v3uLsXTOKpnnGHJYI2qsdA== - -"@nomicfoundation/solidity-analyzer-linux-x64-musl@0.1.1": - version "0.1.1" - resolved "https://registry.yarnpkg.com/@nomicfoundation/solidity-analyzer-linux-x64-musl/-/solidity-analyzer-linux-x64-musl-0.1.1.tgz#9b49d0634b5976bb5ed1604a1e1b736f390959bb" - integrity sha512-KdYMkJOq0SYPQMmErv/63CwGwMm5XHenEna9X9aB8mQmhDBrYrlAOSsIPgFCUSL0hjxE3xHP65/EPXR/InD2+w== - -"@nomicfoundation/solidity-analyzer-win32-arm64-msvc@0.1.1": - version "0.1.1" - resolved "https://registry.yarnpkg.com/@nomicfoundation/solidity-analyzer-win32-arm64-msvc/-/solidity-analyzer-win32-arm64-msvc-0.1.1.tgz#e2867af7264ebbcc3131ef837878955dd6a3676f" - integrity sha512-VFZASBfl4qiBYwW5xeY20exWhmv6ww9sWu/krWSesv3q5hA0o1JuzmPHR4LPN6SUZj5vcqci0O6JOL8BPw+APg== - -"@nomicfoundation/solidity-analyzer-win32-ia32-msvc@0.1.1": - version "0.1.1" - resolved "https://registry.yarnpkg.com/@nomicfoundation/solidity-analyzer-win32-ia32-msvc/-/solidity-analyzer-win32-ia32-msvc-0.1.1.tgz#0685f78608dd516c8cdfb4896ed451317e559585" - integrity sha512-JnFkYuyCSA70j6Si6cS1A9Gh1aHTEb8kOTBApp/c7NRTFGNMH8eaInKlyuuiIbvYFhlXW4LicqyYuWNNq9hkpQ== - -"@nomicfoundation/solidity-analyzer-win32-x64-msvc@0.1.1": - version "0.1.1" - resolved "https://registry.yarnpkg.com/@nomicfoundation/solidity-analyzer-win32-x64-msvc/-/solidity-analyzer-win32-x64-msvc-0.1.1.tgz#c9a44f7108646f083b82e851486e0f6aeb785836" - integrity sha512-HrVJr6+WjIXGnw3Q9u6KQcbZCtk0caVWhCdFADySvRyUxJ8PnzlaP+MhwNE8oyT8OZ6ejHBRrrgjSqDCFXGirw== +"@nomicfoundation/solidity-analyzer-darwin-arm64@0.1.2": + version "0.1.2" + resolved "https://registry.yarnpkg.com/@nomicfoundation/solidity-analyzer-darwin-arm64/-/solidity-analyzer-darwin-arm64-0.1.2.tgz#3a9c3b20d51360b20affb8f753e756d553d49557" + integrity sha512-JaqcWPDZENCvm++lFFGjrDd8mxtf+CtLd2MiXvMNTBD33dContTZ9TWETwNFwg7JTJT5Q9HEecH7FA+HTSsIUw== + +"@nomicfoundation/solidity-analyzer-darwin-x64@0.1.2": + version "0.1.2" + resolved "https://registry.yarnpkg.com/@nomicfoundation/solidity-analyzer-darwin-x64/-/solidity-analyzer-darwin-x64-0.1.2.tgz#74dcfabeb4ca373d95bd0d13692f44fcef133c28" + integrity sha512-fZNmVztrSXC03e9RONBT+CiksSeYcxI1wlzqyr0L7hsQlK1fzV+f04g2JtQ1c/Fe74ZwdV6aQBdd6Uwl1052sw== + +"@nomicfoundation/solidity-analyzer-linux-arm64-gnu@0.1.2": + version "0.1.2" + resolved "https://registry.yarnpkg.com/@nomicfoundation/solidity-analyzer-linux-arm64-gnu/-/solidity-analyzer-linux-arm64-gnu-0.1.2.tgz#4af5849a89e5a8f511acc04f28eb5d4460ba2b6a" + integrity sha512-3d54oc+9ZVBuB6nbp8wHylk4xh0N0Gc+bk+/uJae+rUgbOBwQSfuGIbAZt1wBXs5REkSmynEGcqx6DutoK0tPA== + +"@nomicfoundation/solidity-analyzer-linux-arm64-musl@0.1.2": + version "0.1.2" + resolved "https://registry.yarnpkg.com/@nomicfoundation/solidity-analyzer-linux-arm64-musl/-/solidity-analyzer-linux-arm64-musl-0.1.2.tgz#54036808a9a327b2ff84446c130a6687ee702a8e" + integrity sha512-iDJfR2qf55vgsg7BtJa7iPiFAsYf2d0Tv/0B+vhtnI16+wfQeTbP7teookbGvAo0eJo7aLLm0xfS/GTkvHIucA== + +"@nomicfoundation/solidity-analyzer-linux-x64-gnu@0.1.2": + version "0.1.2" + resolved "https://registry.yarnpkg.com/@nomicfoundation/solidity-analyzer-linux-x64-gnu/-/solidity-analyzer-linux-x64-gnu-0.1.2.tgz#466cda0d6e43691986c944b909fc6dbb8cfc594e" + integrity sha512-9dlHMAt5/2cpWyuJ9fQNOUXFB/vgSFORg1jpjX1Mh9hJ/MfZXlDdHQ+DpFCs32Zk5pxRBb07yGvSHk9/fezL+g== + +"@nomicfoundation/solidity-analyzer-linux-x64-musl@0.1.2": + version "0.1.2" + resolved "https://registry.yarnpkg.com/@nomicfoundation/solidity-analyzer-linux-x64-musl/-/solidity-analyzer-linux-x64-musl-0.1.2.tgz#2b35826987a6e94444140ac92310baa088ee7f94" + integrity sha512-GzzVeeJob3lfrSlDKQw2bRJ8rBf6mEYaWY+gW0JnTDHINA0s2gPR4km5RLIj1xeZZOYz4zRw+AEeYgLRqB2NXg== + +"@nomicfoundation/solidity-analyzer-win32-x64-msvc@0.1.2": + version "0.1.2" + resolved "https://registry.yarnpkg.com/@nomicfoundation/solidity-analyzer-win32-x64-msvc/-/solidity-analyzer-win32-x64-msvc-0.1.2.tgz#e6363d13b8709ca66f330562337dbc01ce8bbbd9" + integrity sha512-Fdjli4DCcFHb4Zgsz0uEJXZ2K7VEO+w5KVv7HmT7WO10iODdU9csC2az4jrhEsRtiR9Gfd74FlG0NYlw1BMdyA== "@nomicfoundation/solidity-analyzer@^0.1.0": - version "0.1.1" - resolved "https://registry.yarnpkg.com/@nomicfoundation/solidity-analyzer/-/solidity-analyzer-0.1.1.tgz#f5f4d36d3f66752f59a57e7208cd856f3ddf6f2d" - integrity sha512-1LMtXj1puAxyFusBgUIy5pZk3073cNXYnXUpuNKFghHbIit/xZgbk0AokpUADbNm3gyD6bFWl3LRFh3dhVdREg== + version "0.1.2" + resolved "https://registry.yarnpkg.com/@nomicfoundation/solidity-analyzer/-/solidity-analyzer-0.1.2.tgz#8bcea7d300157bf3a770a851d9f5c5e2db34ac55" + integrity sha512-q4n32/FNKIhQ3zQGGw5CvPF6GTvDCpYwIf7bEY/dZTZbgfDsHyjJwURxUJf3VQuuJj+fDIFl4+KkBVbw4Ef6jA== optionalDependencies: - "@nomicfoundation/solidity-analyzer-darwin-arm64" "0.1.1" - "@nomicfoundation/solidity-analyzer-darwin-x64" "0.1.1" - "@nomicfoundation/solidity-analyzer-freebsd-x64" "0.1.1" - "@nomicfoundation/solidity-analyzer-linux-arm64-gnu" "0.1.1" - "@nomicfoundation/solidity-analyzer-linux-arm64-musl" "0.1.1" - "@nomicfoundation/solidity-analyzer-linux-x64-gnu" "0.1.1" - "@nomicfoundation/solidity-analyzer-linux-x64-musl" "0.1.1" - "@nomicfoundation/solidity-analyzer-win32-arm64-msvc" "0.1.1" - "@nomicfoundation/solidity-analyzer-win32-ia32-msvc" "0.1.1" - "@nomicfoundation/solidity-analyzer-win32-x64-msvc" "0.1.1" + "@nomicfoundation/solidity-analyzer-darwin-arm64" "0.1.2" + "@nomicfoundation/solidity-analyzer-darwin-x64" "0.1.2" + "@nomicfoundation/solidity-analyzer-linux-arm64-gnu" "0.1.2" + "@nomicfoundation/solidity-analyzer-linux-arm64-musl" "0.1.2" + "@nomicfoundation/solidity-analyzer-linux-x64-gnu" "0.1.2" + "@nomicfoundation/solidity-analyzer-linux-x64-musl" "0.1.2" + "@nomicfoundation/solidity-analyzer-win32-x64-msvc" "0.1.2" "@nomiclabs/hardhat-ethers@2.2.2": version "2.2.2" @@ -769,22 +720,24 @@ solhint "^2.0.0" "@openzeppelin/contracts-upgradeable@^4.9.2": - version "4.9.2" - resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-4.9.2.tgz#a817c75688f8daede420052fbcb34e52482e769e" - integrity sha512-siviV3PZV/fHfPaoIC51rf1Jb6iElkYWnNYZ0leO23/ukXuvOyoC/ahy8jqiV7g+++9Nuo3n/rk5ajSN/+d/Sg== + version "4.9.6" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-4.9.6.tgz#38b21708a719da647de4bb0e4802ee235a0d24df" + integrity sha512-m4iHazOsOCv1DgM7eD7GupTJ+NFVujRZt1wzddDPSVGpWdKq1SKkla5htKG7+IS4d2XOCtzkUNwRZ7Vq5aEUMA== "@openzeppelin/contracts@^4.7.3": - version "4.8.2" - resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.8.2.tgz#d815ade0027b50beb9bcca67143c6bcc3e3923d6" - integrity sha512-kEUOgPQszC0fSYWpbh2kT94ltOJwj1qfT2DWo+zVttmGmf97JZ99LspePNaeeaLhCImaHVeBbjaQFZQn7+Zc5g== + version "4.9.6" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.9.6.tgz#2a880a24eb19b4f8b25adc2a5095f2aa27f39677" + integrity sha512-xSmezSupL+y9VkHZJGDoCBpmnB2ogM13ccaYDWqJTfS3dbuHkgjuwDFUmaFauBCboQMGB/S5UqUl2y54X99BmA== "@openzeppelin/merkle-tree@^1.0.4": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@openzeppelin/merkle-tree/-/merkle-tree-1.0.4.tgz#9019c2990ffdb56fca232c210d1e1132cbd68924" - integrity sha512-gNMYRgIn7mkPWpE2sBGfE12fFM5pfbGrDr0leaMdCkmFyccQlqWYL4p0FedcVqN6X0ETJGTa+UKF1fUWAzv9VQ== + version "1.0.7" + resolved "https://registry.yarnpkg.com/@openzeppelin/merkle-tree/-/merkle-tree-1.0.7.tgz#88f815df8ba39033e312e5f3dc6debeb62845916" + integrity sha512-i93t0YYv6ZxTCYU3CdO5Q+DXK0JH10A4dCBOMlzYbX+ujTXm+k1lXiEyVqmf94t3sqmv8sm/XT5zTa0+efnPgQ== dependencies: "@ethersproject/abi" "^5.7.0" - ethereum-cryptography "^1.1.2" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" "@prb/math@^2.5.0": version "2.5.0" @@ -796,10 +749,10 @@ evm-bn "^1.1.1" mathjs "^10.4.0" -"@scure/base@~1.1.0": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.1.tgz#ebb651ee52ff84f420097055f4bf46cfba403938" - integrity sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA== +"@scure/base@~1.1.0", "@scure/base@~1.1.6": + version "1.1.7" + resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.7.tgz#fe973311a5c6267846aa131bc72e96c5d40d2b30" + integrity sha512-PPNYBslrLNNUQ/Yad37MHYsNQtK67EhWb6WtSvNLLPo7SdVZgkUjD6Dg+5On7zNwmskf8OX7I7Nx5oN+MIWE0g== "@scure/bip32@1.1.5": version "1.1.5" @@ -810,6 +763,15 @@ "@noble/secp256k1" "~1.7.0" "@scure/base" "~1.1.0" +"@scure/bip32@1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.4.0.tgz#4e1f1e196abedcef395b33b9674a042524e20d67" + integrity sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg== + dependencies: + "@noble/curves" "~1.4.0" + "@noble/hashes" "~1.4.0" + "@scure/base" "~1.1.6" + "@scure/bip39@1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.1.1.tgz#b54557b2e86214319405db819c4b6a370cf340c5" @@ -818,6 +780,14 @@ "@noble/hashes" "~1.2.0" "@scure/base" "~1.1.0" +"@scure/bip39@1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.3.0.tgz#0f258c16823ddd00739461ac31398b4e7d6a18c3" + integrity sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ== + dependencies: + "@noble/hashes" "~1.4.0" + "@scure/base" "~1.1.6" + "@sentry/core@5.30.0": version "5.30.0" resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.30.0.tgz#6b203664f69e75106ee8b5a2fe1d717379b331f3" @@ -886,24 +856,27 @@ "@sentry/types" "5.30.0" tslib "^1.9.3" -"@solidity-parser/parser@^0.14.0", "@solidity-parser/parser@^0.14.1": +"@solidity-parser/parser@^0.14.0": version "0.14.5" resolved "https://registry.yarnpkg.com/@solidity-parser/parser/-/parser-0.14.5.tgz#87bc3cc7b068e08195c219c91cd8ddff5ef1a804" integrity sha512-6dKnHZn7fg/iQATVEzqyUOyEidbn05q7YA2mQ9hC0MMXhhV3/JrsxmFSYZAcr7j1yUP700LLhTruvJ3MiQmjJg== dependencies: antlr4ts "^0.5.0-alpha.4" -"@solidity-parser/parser@^0.16.0": - version "0.16.0" - resolved "https://registry.yarnpkg.com/@solidity-parser/parser/-/parser-0.16.0.tgz#1fb418c816ca1fc3a1e94b08bcfe623ec4e1add4" - integrity sha512-ESipEcHyRHg4Np4SqBCfcXwyxxna1DgFVz69bgpLV8vzl/NP1DtcKsJ4dJZXWQhY/Z4J2LeKBiOkOVZn9ct33Q== - dependencies: - antlr4ts "^0.5.0-alpha.4" +"@solidity-parser/parser@^0.17.0": + version "0.17.0" + resolved "https://registry.yarnpkg.com/@solidity-parser/parser/-/parser-0.17.0.tgz#52a2fcc97ff609f72011014e4c5b485ec52243ef" + integrity sha512-Nko8R0/kUo391jsEHHxrGM07QFdnPGvlmox4rmH0kNiNAashItAilhy4Mv4pK5gQmW5f4sXAF58fwJbmlkGcVw== + +"@solidity-parser/parser@^0.18.0": + version "0.18.0" + resolved "https://registry.yarnpkg.com/@solidity-parser/parser/-/parser-0.18.0.tgz#8e77a02a09ecce957255a2f48c9a7178ec191908" + integrity sha512-yfORGUIPgLck41qyN7nbwJRAx17/jAIXCTanHOJZhB6PJ1iAk/84b/xlsVKFSyNyLXIj0dhppoE0+CRws7wlzA== "@tsconfig/node10@^1.0.7": - version "1.0.9" - resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2" - integrity sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA== + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.11.tgz#6ee46400685f130e278128c7b38b7e031ff5b2f2" + integrity sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw== "@tsconfig/node12@^1.0.7": version "1.0.11" @@ -916,30 +889,25 @@ integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== "@tsconfig/node16@^1.0.2": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.3.tgz#472eaab5f15c1ffdd7f8628bd4c4f753995ec79e" - integrity sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ== + version "1.0.4" + resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" + integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== "@typechain/ethers-v5@^10.1.0": - version "10.2.0" - resolved "https://registry.yarnpkg.com/@typechain/ethers-v5/-/ethers-v5-10.2.0.tgz#68f5963efb5214cb2d881477228e4b5b315473e1" - integrity sha512-ikaq0N/w9fABM+G01OFmU3U3dNnyRwEahkdvi9mqy1a3XwKiPZaF/lu54OcNaEWnpvEYyhhS0N7buCtLQqC92w== + version "10.2.1" + resolved "https://registry.yarnpkg.com/@typechain/ethers-v5/-/ethers-v5-10.2.1.tgz#50241e6957683281ecfa03fb5a6724d8a3ce2391" + integrity sha512-n3tQmCZjRE6IU4h6lqUGiQ1j866n5MTCBJreNEHHVWXa2u9GJTaeYyU1/k+1qLutkyw+sS6VAN+AbeiTqsxd/A== dependencies: lodash "^4.17.15" ts-essentials "^7.0.1" "@typechain/hardhat@^6.1.2": - version "6.1.5" - resolved "https://registry.yarnpkg.com/@typechain/hardhat/-/hardhat-6.1.5.tgz#caad58a1d3e9cd88061a584eb4f4fa763d5dcad1" - integrity sha512-lg7LW4qDZpxFMknp3Xool61Fg6Lays8F8TXdFGBG+MxyYcYU5795P1U2XdStuzGq9S2Dzdgh+1jGww9wvZ6r4Q== + version "6.1.6" + resolved "https://registry.yarnpkg.com/@typechain/hardhat/-/hardhat-6.1.6.tgz#1a749eb35e5054c80df531cf440819cb347c62ea" + integrity sha512-BiVnegSs+ZHVymyidtK472syodx1sXYlYJJixZfRstHVGYTi8V1O7QG4nsjyb0PC/LORcq7sfBUcHto1y6UgJA== dependencies: fs-extra "^9.1.0" -"@types/async-eventemitter@^0.2.1": - version "0.2.1" - resolved "https://registry.yarnpkg.com/@types/async-eventemitter/-/async-eventemitter-0.2.1.tgz#f8e6280e87e8c60b2b938624b0a3530fb3e24712" - integrity sha512-M2P4Ng26QbAeITiH7w1d7OxtldgfAe0wobpyJzVK/XOb0cUGKU2R4pfAhqcJBXAe2ife5ZOhSv4wk7p+ffURtg== - "@types/bn.js@^4.11.3": version "4.11.6" resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-4.11.6.tgz#c306c70d9358aaea33cd4eda092a742b9505967c" @@ -948,23 +916,23 @@ "@types/node" "*" "@types/bn.js@^5.1.0": - version "5.1.1" - resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-5.1.1.tgz#b51e1b55920a4ca26e9285ff79936bbdec910682" - integrity sha512-qNrYbZqMx0uJAfKnKclPh+dTwK33KfLHYqtyODwd5HnXOjnkhc4qgn3BrK6RWyGZm5+sIFE7Q7Vz6QQtJB7w7g== + version "5.1.5" + resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-5.1.5.tgz#2e0dacdcce2c0f16b905d20ff87aedbc6f7b4bf0" + integrity sha512-V46N0zwKRF5Q00AZ6hWtN0T8gGmDUaUzLWQvHFo5yThtVwK/VCenFY3wXVbOvNfajEpsTfQM4IN9k/d6gUVX3A== dependencies: "@types/node" "*" "@types/chai-as-promised@^7.1.3": - version "7.1.5" - resolved "https://registry.yarnpkg.com/@types/chai-as-promised/-/chai-as-promised-7.1.5.tgz#6e016811f6c7a64f2eed823191c3a6955094e255" - integrity sha512-jStwss93SITGBwt/niYrkf2C+/1KTeZCZl1LaeezTlqppAKeoQC7jxyqYuP72sxBGKCIbw7oHgbYssIRzT5FCQ== + version "7.1.8" + resolved "https://registry.yarnpkg.com/@types/chai-as-promised/-/chai-as-promised-7.1.8.tgz#f2b3d82d53c59626b5d6bbc087667ccb4b677fe9" + integrity sha512-ThlRVIJhr69FLlh6IctTXFkmhtP3NpMZ2QGq69StYLyKZFp/HOp1VdKZj7RvfNWYYcJ1xlbLGLLWj1UvP5u/Gw== dependencies: "@types/chai" "*" "@types/chai@*", "@types/chai@^4.2.0": - version "4.3.4" - resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.4.tgz#e913e8175db8307d78b4e8fa690408ba6b65dee4" - integrity sha512-KnRanxnpfpjUTqTCXslZSEdLfXExwgNxYPdiO2WGUj8+HDjFi8R3k5RVKPeSCzLjCcshCAtVO2QBbVuAV4kTnw== + version "4.3.16" + resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.16.tgz#b1572967f0b8b60bf3f87fe1d854a5604ea70c82" + integrity sha512-PatH4iOdyh3MyWtmHVFXLWCCIhUbopaltqddG9BzB+gMIzee2MJrvd+jouii9Z3wzQJruGWAm7WOMjgfG8hQlQ== "@types/concat-stream@^1.6.0": version "1.6.1" @@ -974,30 +942,25 @@ "@types/node" "*" "@types/eslint-scope@^3.7.3": - version "3.7.4" - resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.4.tgz#37fc1223f0786c39627068a12e94d6e6fc61de16" - integrity sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA== + version "3.7.7" + resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.7.tgz#3108bd5f18b0cdb277c867b3dd449c9ed7079ac5" + integrity sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg== dependencies: "@types/eslint" "*" "@types/estree" "*" "@types/eslint@*": - version "8.21.3" - resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.21.3.tgz#5794b3911f0f19e34e3a272c49cbdf48d6f543f2" - integrity sha512-fa7GkppZVEByMWGbTtE5MbmXWJTVbrjjaS8K6uQj+XtuuUv1fsuPAxhygfqLmsb/Ufb3CV8deFCpiMfAgi00Sw== + version "9.6.0" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-9.6.0.tgz#51d4fe4d0316da9e9f2c80884f2c20ed5fb022ff" + integrity sha512-gi6WQJ7cHRgZxtkQEoyHMppPjq9Kxo5Tjn2prSKDSmZrCz8TZ3jSRCeTJm+WoM+oB0WG37bRqLzaaU3q7JypGg== dependencies: "@types/estree" "*" "@types/json-schema" "*" -"@types/estree@*": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.0.tgz#5fb2e536c1ae9bf35366eed879e827fa59ca41c2" - integrity sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ== - -"@types/estree@^0.0.51": - version "0.0.51" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.51.tgz#cfd70924a25a3fd32b218e5e420e6897e1ac4f40" - integrity sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ== +"@types/estree@*", "@types/estree@^1.0.5": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" + integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== "@types/form-data@0.0.33": version "0.0.33" @@ -1027,9 +990,9 @@ integrity sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg== "@types/json-schema@*", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": - version "7.0.11" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" - integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== + version "7.0.15" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== "@types/json5@^0.0.29": version "0.0.29" @@ -1052,9 +1015,11 @@ integrity sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw== "@types/node@*", "@types/node@>=12.0.0": - version "18.15.11" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.11.tgz#b3b790f09cb1696cffcec605de025b088fa4225f" - integrity sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q== + version "22.0.0" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.0.0.tgz#04862a2a71e62264426083abe1e27e87cac05a30" + integrity sha512-VT7KSYudcPOzP5Q0wfbowyNLaVR8QWUdw+088uFWwfvpY6uCWaXpqV6ieLAu9WBcnTa7H4Z5RLK8I5t2FuOcqw== + dependencies: + undici-types "~6.11.1" "@types/node@^10.0.3": version "10.17.60" @@ -1067,133 +1032,140 @@ integrity sha512-tktOkFUA4kXx2hhhrB8bIFb5TbwzS4uOhKEmwiD+NoiL0qtP2OQ9mFldbgD4dV1djrlBYP6eBuQZiWjuHUpqFw== "@types/pbkdf2@^3.0.0": - version "3.1.0" - resolved "https://registry.yarnpkg.com/@types/pbkdf2/-/pbkdf2-3.1.0.tgz#039a0e9b67da0cdc4ee5dab865caa6b267bb66b1" - integrity sha512-Cf63Rv7jCQ0LaL8tNXmEyqTHuIJxRdlS5vMh1mj5voN4+QFhVZnlZruezqpWYDiJ8UTzhP0VmeLXCmBk66YrMQ== + version "3.1.2" + resolved "https://registry.yarnpkg.com/@types/pbkdf2/-/pbkdf2-3.1.2.tgz#2dc43808e9985a2c69ff02e2d2027bd4fe33e8dc" + integrity sha512-uRwJqmiXmh9++aSu1VNEn3iIxWOhd8AHXNSdlaLfdAAdSTY9jYVeGWnzejM3dvrkbqE3/hyQkQQ29IFATEGlew== dependencies: "@types/node" "*" "@types/prettier@^2.1.1": - version "2.7.2" - resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.2.tgz#6c2324641cc4ba050a8c710b2b251b377581fbf0" - integrity sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg== + version "2.7.3" + resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.3.tgz#3e51a17e291d01d17d3fc61422015a933af7a08f" + integrity sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA== "@types/qs@^6.2.31", "@types/qs@^6.9.7": - version "6.9.7" - resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" - integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== + version "6.9.15" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.15.tgz#adde8a060ec9c305a82de1babc1056e73bd64dce" + integrity sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg== "@types/secp256k1@^4.0.1": - version "4.0.3" - resolved "https://registry.yarnpkg.com/@types/secp256k1/-/secp256k1-4.0.3.tgz#1b8e55d8e00f08ee7220b4d59a6abe89c37a901c" - integrity sha512-Da66lEIFeIz9ltsdMZcpQvmrmmoqrfju8pm1BH8WbYjZSwUgCwXLb9C+9XYogwBITnbsSaMdVPb2ekf7TV+03w== + version "4.0.6" + resolved "https://registry.yarnpkg.com/@types/secp256k1/-/secp256k1-4.0.6.tgz#d60ba2349a51c2cbc5e816dcd831a42029d376bf" + integrity sha512-hHxJU6PAEUn0TP4S/ZOzuTUvJWuZ6eIKeNKb5RBpODvSl6hp1Wrw4s7ATY50rklRCScUDpHzVA/DQdSjJ3UoYQ== dependencies: "@types/node" "*" "@types/semver@^7.3.12": - version "7.3.13" - resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.13.tgz#da4bfd73f49bd541d28920ab0e2bf0ee80f71c91" - integrity sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw== + version "7.5.8" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.8.tgz#8268a8c57a3e4abd25c165ecd36237db7948a55e" + integrity sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ== "@typescript-eslint/eslint-plugin@^5.49.0": - version "5.57.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.57.0.tgz#52c8a7a4512f10e7249ca1e2e61f81c62c34365c" - integrity sha512-itag0qpN6q2UMM6Xgk6xoHa0D0/P+M17THnr4SVgqn9Rgam5k/He33MA7/D7QoJcdMxHFyX7U9imaBonAX/6qA== + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz#aeef0328d172b9e37d9bab6dbc13b87ed88977db" + integrity sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag== dependencies: "@eslint-community/regexpp" "^4.4.0" - "@typescript-eslint/scope-manager" "5.57.0" - "@typescript-eslint/type-utils" "5.57.0" - "@typescript-eslint/utils" "5.57.0" + "@typescript-eslint/scope-manager" "5.62.0" + "@typescript-eslint/type-utils" "5.62.0" + "@typescript-eslint/utils" "5.62.0" debug "^4.3.4" - grapheme-splitter "^1.0.4" + graphemer "^1.4.0" ignore "^5.2.0" natural-compare-lite "^1.4.0" semver "^7.3.7" tsutils "^3.21.0" "@typescript-eslint/experimental-utils@^5.0.0": - version "5.57.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-5.57.0.tgz#e4ddb5f1c77f5be73e7d0435c8d0bf3196b9d2ed" - integrity sha512-0RnrwGQ7MmgtOSnzB/rSGYr2iXENi6L+CtPzX3g5ovo0HlruLukSEKcc4s+q0IEc+DLTDc7Edan0Y4WSQ/bFhw== + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-5.62.0.tgz#14559bf73383a308026b427a4a6129bae2146741" + integrity sha512-RTXpeB3eMkpoclG3ZHft6vG/Z30azNHuqY6wKPBHlVMZFuEvrtlEDe8gMqDb+SO+9hjC/pLekeSCryf9vMZlCw== dependencies: - "@typescript-eslint/utils" "5.57.0" + "@typescript-eslint/utils" "5.62.0" "@typescript-eslint/parser@^5.49.0": - version "5.57.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.57.0.tgz#f675bf2cd1a838949fd0de5683834417b757e4fa" - integrity sha512-orrduvpWYkgLCyAdNtR1QIWovcNZlEm6yL8nwH/eTxWLd8gsP+25pdLHYzL2QdkqrieaDwLpytHqycncv0woUQ== + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.62.0.tgz#1b63d082d849a2fcae8a569248fbe2ee1b8a56c7" + integrity sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA== dependencies: - "@typescript-eslint/scope-manager" "5.57.0" - "@typescript-eslint/types" "5.57.0" - "@typescript-eslint/typescript-estree" "5.57.0" + "@typescript-eslint/scope-manager" "5.62.0" + "@typescript-eslint/types" "5.62.0" + "@typescript-eslint/typescript-estree" "5.62.0" debug "^4.3.4" -"@typescript-eslint/scope-manager@5.57.0": - version "5.57.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.57.0.tgz#79ccd3fa7bde0758059172d44239e871e087ea36" - integrity sha512-NANBNOQvllPlizl9LatX8+MHi7bx7WGIWYjPHDmQe5Si/0YEYfxSljJpoTyTWFTgRy3X8gLYSE4xQ2U+aCozSw== +"@typescript-eslint/scope-manager@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz#d9457ccc6a0b8d6b37d0eb252a23022478c5460c" + integrity sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w== dependencies: - "@typescript-eslint/types" "5.57.0" - "@typescript-eslint/visitor-keys" "5.57.0" + "@typescript-eslint/types" "5.62.0" + "@typescript-eslint/visitor-keys" "5.62.0" -"@typescript-eslint/type-utils@5.57.0": - version "5.57.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.57.0.tgz#98e7531c4e927855d45bd362de922a619b4319f2" - integrity sha512-kxXoq9zOTbvqzLbdNKy1yFrxLC6GDJFE2Yuo3KqSwTmDOFjUGeWSakgoXT864WcK5/NAJkkONCiKb1ddsqhLXQ== +"@typescript-eslint/type-utils@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz#286f0389c41681376cdad96b309cedd17d70346a" + integrity sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew== dependencies: - "@typescript-eslint/typescript-estree" "5.57.0" - "@typescript-eslint/utils" "5.57.0" + "@typescript-eslint/typescript-estree" "5.62.0" + "@typescript-eslint/utils" "5.62.0" debug "^4.3.4" tsutils "^3.21.0" -"@typescript-eslint/types@5.57.0": - version "5.57.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.57.0.tgz#727bfa2b64c73a4376264379cf1f447998eaa132" - integrity sha512-mxsod+aZRSyLT+jiqHw1KK6xrANm19/+VFALVFP5qa/aiJnlP38qpyaTd0fEKhWvQk6YeNZ5LGwI1pDpBRBhtQ== +"@typescript-eslint/types@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.62.0.tgz#258607e60effa309f067608931c3df6fed41fd2f" + integrity sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ== -"@typescript-eslint/typescript-estree@5.57.0": - version "5.57.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.57.0.tgz#ebcd0ee3e1d6230e888d88cddf654252d41e2e40" - integrity sha512-LTzQ23TV82KpO8HPnWuxM2V7ieXW8O142I7hQTxWIHDcCEIjtkat6H96PFkYBQqGFLW/G/eVVOB9Z8rcvdY/Vw== +"@typescript-eslint/typescript-estree@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz#7d17794b77fabcac615d6a48fb143330d962eb9b" + integrity sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA== dependencies: - "@typescript-eslint/types" "5.57.0" - "@typescript-eslint/visitor-keys" "5.57.0" + "@typescript-eslint/types" "5.62.0" + "@typescript-eslint/visitor-keys" "5.62.0" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/utils@5.57.0", "@typescript-eslint/utils@^5.10.0": - version "5.57.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.57.0.tgz#eab8f6563a2ac31f60f3e7024b91bf75f43ecef6" - integrity sha512-ps/4WohXV7C+LTSgAL5CApxvxbMkl9B9AUZRtnEFonpIxZDIT7wC1xfvuJONMidrkB9scs4zhtRyIwHh4+18kw== +"@typescript-eslint/utils@5.62.0", "@typescript-eslint/utils@^5.10.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.62.0.tgz#141e809c71636e4a75daa39faed2fb5f4b10df86" + integrity sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ== dependencies: "@eslint-community/eslint-utils" "^4.2.0" "@types/json-schema" "^7.0.9" "@types/semver" "^7.3.12" - "@typescript-eslint/scope-manager" "5.57.0" - "@typescript-eslint/types" "5.57.0" - "@typescript-eslint/typescript-estree" "5.57.0" + "@typescript-eslint/scope-manager" "5.62.0" + "@typescript-eslint/types" "5.62.0" + "@typescript-eslint/typescript-estree" "5.62.0" eslint-scope "^5.1.1" semver "^7.3.7" -"@typescript-eslint/visitor-keys@5.57.0": - version "5.57.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.57.0.tgz#e2b2f4174aff1d15eef887ce3d019ecc2d7a8ac1" - integrity sha512-ery2g3k0hv5BLiKpPuwYt9KBkAp2ugT6VvyShXdLOkax895EC55sP0Tx5L0fZaQueiK3fBLvHVvEl3jFS5ia+g== +"@typescript-eslint/visitor-keys@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz#2174011917ce582875954ffe2f6912d5931e353e" + integrity sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw== dependencies: - "@typescript-eslint/types" "5.57.0" + "@typescript-eslint/types" "5.62.0" eslint-visitor-keys "^3.3.0" -"@vue/compiler-sfc@2.7.14": - version "2.7.14" - resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-2.7.14.tgz#3446fd2fbb670d709277fc3ffa88efc5e10284fd" - integrity sha512-aNmNHyLPsw+sVvlQFQ2/8sjNuLtK54TC6cuKnVzAY93ks4ZBrvwQSnkkIh7bsbNhum5hJBS00wSDipQ937f5DA== +"@ungap/structured-clone@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" + integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== + +"@vue/compiler-sfc@2.7.16": + version "2.7.16" + resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-2.7.16.tgz#ff81711a0fac9c68683d8bb00b63f857de77dc83" + integrity sha512-KWhJ9k5nXuNtygPU7+t1rX6baZeqOYLEforUPjgNDBnLicfHCoi48H87Q8XyLZOrNNsmhuwKqtpDQWjEFe6Ekg== dependencies: - "@babel/parser" "^7.18.4" + "@babel/parser" "^7.23.5" postcss "^8.4.14" source-map "^0.6.1" + optionalDependencies: + prettier "^1.18.2 || ^2.0.0" "@vue/component-compiler-utils@^3.1.0": version "3.3.0" @@ -1211,125 +1183,125 @@ optionalDependencies: prettier "^1.18.2 || ^2.0.0" -"@webassemblyjs/ast@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.1.tgz#2bfd767eae1a6996f432ff7e8d7fc75679c0b6a7" - integrity sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw== +"@webassemblyjs/ast@1.12.1", "@webassemblyjs/ast@^1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.12.1.tgz#bb16a0e8b1914f979f45864c23819cc3e3f0d4bb" + integrity sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg== dependencies: - "@webassemblyjs/helper-numbers" "1.11.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.1" + "@webassemblyjs/helper-numbers" "1.11.6" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" -"@webassemblyjs/floating-point-hex-parser@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz#f6c61a705f0fd7a6aecaa4e8198f23d9dc179e4f" - integrity sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ== +"@webassemblyjs/floating-point-hex-parser@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz#dacbcb95aff135c8260f77fa3b4c5fea600a6431" + integrity sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw== -"@webassemblyjs/helper-api-error@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz#1a63192d8788e5c012800ba6a7a46c705288fd16" - integrity sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg== +"@webassemblyjs/helper-api-error@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz#6132f68c4acd59dcd141c44b18cbebbd9f2fa768" + integrity sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q== -"@webassemblyjs/helper-buffer@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz#832a900eb444884cde9a7cad467f81500f5e5ab5" - integrity sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA== +"@webassemblyjs/helper-buffer@1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz#6df20d272ea5439bf20ab3492b7fb70e9bfcb3f6" + integrity sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw== -"@webassemblyjs/helper-numbers@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz#64d81da219fbbba1e3bd1bfc74f6e8c4e10a62ae" - integrity sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ== +"@webassemblyjs/helper-numbers@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz#cbce5e7e0c1bd32cf4905ae444ef64cea919f1b5" + integrity sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g== dependencies: - "@webassemblyjs/floating-point-hex-parser" "1.11.1" - "@webassemblyjs/helper-api-error" "1.11.1" + "@webassemblyjs/floating-point-hex-parser" "1.11.6" + "@webassemblyjs/helper-api-error" "1.11.6" "@xtuc/long" "4.2.2" -"@webassemblyjs/helper-wasm-bytecode@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz#f328241e41e7b199d0b20c18e88429c4433295e1" - integrity sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q== +"@webassemblyjs/helper-wasm-bytecode@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz#bb2ebdb3b83aa26d9baad4c46d4315283acd51e9" + integrity sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA== -"@webassemblyjs/helper-wasm-section@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz#21ee065a7b635f319e738f0dd73bfbda281c097a" - integrity sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg== +"@webassemblyjs/helper-wasm-section@1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz#3da623233ae1a60409b509a52ade9bc22a37f7bf" + integrity sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g== dependencies: - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/helper-buffer" "1.11.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.1" - "@webassemblyjs/wasm-gen" "1.11.1" + "@webassemblyjs/ast" "1.12.1" + "@webassemblyjs/helper-buffer" "1.12.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + "@webassemblyjs/wasm-gen" "1.12.1" -"@webassemblyjs/ieee754@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz#963929e9bbd05709e7e12243a099180812992614" - integrity sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ== +"@webassemblyjs/ieee754@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz#bb665c91d0b14fffceb0e38298c329af043c6e3a" + integrity sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg== dependencies: "@xtuc/ieee754" "^1.2.0" -"@webassemblyjs/leb128@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.11.1.tgz#ce814b45574e93d76bae1fb2644ab9cdd9527aa5" - integrity sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw== +"@webassemblyjs/leb128@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.11.6.tgz#70e60e5e82f9ac81118bc25381a0b283893240d7" + integrity sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ== dependencies: "@xtuc/long" "4.2.2" -"@webassemblyjs/utf8@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.1.tgz#d1f8b764369e7c6e6bae350e854dec9a59f0a3ff" - integrity sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ== - -"@webassemblyjs/wasm-edit@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz#ad206ebf4bf95a058ce9880a8c092c5dec8193d6" - integrity sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA== - dependencies: - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/helper-buffer" "1.11.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.1" - "@webassemblyjs/helper-wasm-section" "1.11.1" - "@webassemblyjs/wasm-gen" "1.11.1" - "@webassemblyjs/wasm-opt" "1.11.1" - "@webassemblyjs/wasm-parser" "1.11.1" - "@webassemblyjs/wast-printer" "1.11.1" - -"@webassemblyjs/wasm-gen@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz#86c5ea304849759b7d88c47a32f4f039ae3c8f76" - integrity sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA== - dependencies: - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.1" - "@webassemblyjs/ieee754" "1.11.1" - "@webassemblyjs/leb128" "1.11.1" - "@webassemblyjs/utf8" "1.11.1" - -"@webassemblyjs/wasm-opt@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz#657b4c2202f4cf3b345f8a4c6461c8c2418985f2" - integrity sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw== - dependencies: - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/helper-buffer" "1.11.1" - "@webassemblyjs/wasm-gen" "1.11.1" - "@webassemblyjs/wasm-parser" "1.11.1" - -"@webassemblyjs/wasm-parser@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz#86ca734534f417e9bd3c67c7a1c75d8be41fb199" - integrity sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA== - dependencies: - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/helper-api-error" "1.11.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.1" - "@webassemblyjs/ieee754" "1.11.1" - "@webassemblyjs/leb128" "1.11.1" - "@webassemblyjs/utf8" "1.11.1" - -"@webassemblyjs/wast-printer@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz#d0c73beda8eec5426f10ae8ef55cee5e7084c2f0" - integrity sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg== - dependencies: - "@webassemblyjs/ast" "1.11.1" +"@webassemblyjs/utf8@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.6.tgz#90f8bc34c561595fe156603be7253cdbcd0fab5a" + integrity sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA== + +"@webassemblyjs/wasm-edit@^1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz#9f9f3ff52a14c980939be0ef9d5df9ebc678ae3b" + integrity sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g== + dependencies: + "@webassemblyjs/ast" "1.12.1" + "@webassemblyjs/helper-buffer" "1.12.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + "@webassemblyjs/helper-wasm-section" "1.12.1" + "@webassemblyjs/wasm-gen" "1.12.1" + "@webassemblyjs/wasm-opt" "1.12.1" + "@webassemblyjs/wasm-parser" "1.12.1" + "@webassemblyjs/wast-printer" "1.12.1" + +"@webassemblyjs/wasm-gen@1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz#a6520601da1b5700448273666a71ad0a45d78547" + integrity sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w== + dependencies: + "@webassemblyjs/ast" "1.12.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + "@webassemblyjs/ieee754" "1.11.6" + "@webassemblyjs/leb128" "1.11.6" + "@webassemblyjs/utf8" "1.11.6" + +"@webassemblyjs/wasm-opt@1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz#9e6e81475dfcfb62dab574ac2dda38226c232bc5" + integrity sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg== + dependencies: + "@webassemblyjs/ast" "1.12.1" + "@webassemblyjs/helper-buffer" "1.12.1" + "@webassemblyjs/wasm-gen" "1.12.1" + "@webassemblyjs/wasm-parser" "1.12.1" + +"@webassemblyjs/wasm-parser@1.12.1", "@webassemblyjs/wasm-parser@^1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz#c47acb90e6f083391e3fa61d113650eea1e95937" + integrity sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ== + dependencies: + "@webassemblyjs/ast" "1.12.1" + "@webassemblyjs/helper-api-error" "1.11.6" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + "@webassemblyjs/ieee754" "1.11.6" + "@webassemblyjs/leb128" "1.11.6" + "@webassemblyjs/utf8" "1.11.6" + +"@webassemblyjs/wast-printer@1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz#bcecf661d7d1abdaf989d8341a4833e33e2b31ac" + integrity sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA== + dependencies: + "@webassemblyjs/ast" "1.12.1" "@xtuc/long" "4.2.2" "@xtuc/ieee754@^1.2.0": @@ -1352,30 +1324,10 @@ abbrev@1.0.x: resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.0.9.tgz#91b4792588a7738c25f35dd6f63752a2f8776135" integrity sha512-LEyx4aLEC3x6T0UguF6YILf+ntvmOaWsVfENmIW0E9H09vKlLDGelMjjSm0jkDHALj8A8quZ/HapKNigzwge+Q== -abort-controller@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" - integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== - dependencies: - event-target-shim "^5.0.0" - -abstract-level@^1.0.0, abstract-level@^1.0.2, abstract-level@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/abstract-level/-/abstract-level-1.0.3.tgz#78a67d3d84da55ee15201486ab44c09560070741" - integrity sha512-t6jv+xHy+VYwc4xqZMn2Pa9DjcdzvzZmQGRjTFc8spIbRGHgBrEKbPq+rYXc7CCo0lxgYvSgKVg9qZAhpVQSjA== - dependencies: - buffer "^6.0.3" - catering "^2.1.0" - is-buffer "^2.0.5" - level-supports "^4.0.0" - level-transcoder "^1.0.1" - module-error "^1.0.1" - queue-microtask "^1.2.3" - -acorn-import-assertions@^1.7.6: - version "1.8.0" - resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz#ba2b5939ce62c238db6d93d81c9b111b29b855e9" - integrity sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw== +acorn-import-attributes@^1.9.5: + version "1.9.5" + resolved "https://registry.yarnpkg.com/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz#7eb1557b1ba05ef18b5ed0ec67591bfab04688ef" + integrity sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ== acorn-jsx@^5.0.0, acorn-jsx@^5.2.0, acorn-jsx@^5.3.2: version "5.3.2" @@ -1383,9 +1335,11 @@ acorn-jsx@^5.0.0, acorn-jsx@^5.2.0, acorn-jsx@^5.3.2: integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== acorn-walk@^8.1.1: - version "8.2.0" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" - integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== + version "8.3.3" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.3.tgz#9caeac29eefaa0c41e3d4c65137de4d6f34df43e" + integrity sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw== + dependencies: + acorn "^8.11.0" acorn@^6.0.7: version "6.4.2" @@ -1397,15 +1351,10 @@ acorn@^7.1.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== -acorn@^8.4.1, acorn@^8.5.0, acorn@^8.7.1, acorn@^8.8.0: - version "8.8.2" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.2.tgz#1b2f25db02af965399b9776b0c2c391276d37c4a" - integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw== - -address@^1.0.1: - version "1.2.2" - resolved "https://registry.yarnpkg.com/address/-/address-1.2.2.tgz#2b5248dac5485a6390532c6a517fda2e3faac89e" - integrity sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA== +acorn@^8.11.0, acorn@^8.4.1, acorn@^8.7.1, acorn@^8.8.2, acorn@^8.9.0: + version "8.12.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248" + integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg== adm-zip@^0.4.16: version "0.4.16" @@ -1437,7 +1386,7 @@ ajv-keywords@^3.5.2: resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== -ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.5, ajv@^6.6.1, ajv@^6.9.1: +ajv@^6.10.2, ajv@^6.12.4, ajv@^6.12.5, ajv@^6.6.1, ajv@^6.9.1: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== @@ -1452,17 +1401,14 @@ amdefine@>=0.0.4: resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" integrity sha512-S2Hw0TtNkMJhIabBwIojKL9YHO5T0n5eNqWJ7Lrlel/zDbftQpxpapi8tZs3X1HWa+u+QeydGmzzNU0m09+Rcg== -ansi-colors@3.2.3: - version "3.2.3" - resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.3.tgz#57d35b8686e851e2cc04c403f1c00203976a1813" - integrity sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw== - -ansi-colors@4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" - integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== +ansi-align@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.1.tgz#0cdf12e111ace773a86e9a1fad1225c43cb19a59" + integrity sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w== + dependencies: + string-width "^4.1.0" -ansi-colors@^4.1.1: +ansi-colors@^4.1.1, ansi-colors@^4.1.3: version "4.1.3" resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b" integrity sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw== @@ -1518,7 +1464,7 @@ antlr4ts@^0.5.0-alpha.4: resolved "https://registry.yarnpkg.com/antlr4ts/-/antlr4ts-0.5.0-alpha.4.tgz#71702865a87478ed0b40c0709f422cf14d51652a" integrity sha512-WPQDt1B74OfPv/IMS2ekXAKkTZIHl88uMetg6q3OTqgFxZ/dxDXI0EWLyZid/1Pe6hTftyg5N7gel5wNAGxXyQ== -anymatch@~3.1.1, anymatch@~3.1.2: +anymatch@~3.1.2: version "3.1.3" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== @@ -1543,7 +1489,7 @@ argparse@^2.0.1: resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== -aria-query@^5.1.3: +aria-query@~5.1.3: version "5.1.3" resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.1.3.tgz#19db27cd101152773631396f7a95a3b58c22c35e" integrity sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ== @@ -1560,23 +1506,24 @@ array-back@^4.0.1, array-back@^4.0.2: resolved "https://registry.yarnpkg.com/array-back/-/array-back-4.0.2.tgz#8004e999a6274586beeb27342168652fdb89fa1e" integrity sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg== -array-buffer-byte-length@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz#fabe8bc193fea865f317fe7807085ee0dee5aead" - integrity sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A== +array-buffer-byte-length@^1.0.0, array-buffer-byte-length@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz#1e5583ec16763540a27ae52eed99ff899223568f" + integrity sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg== dependencies: - call-bind "^1.0.2" - is-array-buffer "^3.0.1" + call-bind "^1.0.5" + is-array-buffer "^3.0.4" -array-includes@^3.1.5, array-includes@^3.1.6: - version "3.1.6" - resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.6.tgz#9e9e720e194f198266ba9e18c29e6a9b0e4b225f" - integrity sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" - get-intrinsic "^1.1.3" +array-includes@^3.1.6, array-includes@^3.1.7, array-includes@^3.1.8: + version "3.1.8" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.8.tgz#5e370cbe172fdd5dd6530c1d4aadda25281ba97d" + integrity sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-object-atoms "^1.0.0" + get-intrinsic "^1.2.4" is-string "^1.0.7" array-union@^2.1.0: @@ -1589,88 +1536,77 @@ array-uniq@1.0.3: resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" integrity sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q== -array.prototype.flat@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz#ffc6576a7ca3efc2f46a143b9d1dda9b4b3cf5e2" - integrity sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA== +array.prototype.findlastindex@^1.2.3: + version "1.2.5" + resolved "https://registry.yarnpkg.com/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz#8c35a755c72908719453f87145ca011e39334d0d" + integrity sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ== dependencies: - call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" - es-shim-unscopables "^1.0.0" + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + es-shim-unscopables "^1.0.2" -array.prototype.flatmap@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz#1aae7903c2100433cb8261cd4ed310aab5c4a183" - integrity sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ== +array.prototype.flat@^1.3.1, array.prototype.flat@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz#1476217df8cff17d72ee8f3ba06738db5b387d18" + integrity sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA== dependencies: call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" + define-properties "^1.2.0" + es-abstract "^1.22.1" es-shim-unscopables "^1.0.0" -array.prototype.reduce@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/array.prototype.reduce/-/array.prototype.reduce-1.0.5.tgz#6b20b0daa9d9734dd6bc7ea66b5bbce395471eac" - integrity sha512-kDdugMl7id9COE8R7MHF5jWk7Dqt/fs4Pv+JXoICnYwqpjjjbUurz6w5fT5IG6brLdJhv6/VoHB0H7oyIBXd+Q== +array.prototype.flatmap@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz#c9a7c6831db8e719d6ce639190146c24bbd3e527" + integrity sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ== dependencies: call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" - es-array-method-boxes-properly "^1.0.0" - is-string "^1.0.7" + define-properties "^1.2.0" + es-abstract "^1.22.1" + es-shim-unscopables "^1.0.0" + +arraybuffer.prototype.slice@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz#097972f4255e41bc3425e37dc3f6421cf9aefde6" + integrity sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A== + dependencies: + array-buffer-byte-length "^1.0.1" + call-bind "^1.0.5" + define-properties "^1.2.1" + es-abstract "^1.22.3" + es-errors "^1.2.1" + get-intrinsic "^1.2.3" + is-array-buffer "^3.0.4" + is-shared-array-buffer "^1.0.2" asap@~2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA== -asn1@~0.2.3: - version "0.2.6" - resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.6.tgz#0d3a7bb6e64e02a90c0303b31f292868ea09a08d" - integrity sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ== - dependencies: - safer-buffer "~2.1.0" - -assert-plus@1.0.0, assert-plus@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" - integrity sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw== - assertion-error@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw== -ast-types-flow@^0.0.7: - version "0.0.7" - resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.7.tgz#f70b735c6bca1a5c9c22d982c3e39e7feba3bdad" - integrity sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag== +ast-types-flow@^0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.8.tgz#0a85e1c92695769ac13a428bb653e7538bea27d6" + integrity sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ== astral-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== -async-eventemitter@^0.2.4: - version "0.2.4" - resolved "https://registry.yarnpkg.com/async-eventemitter/-/async-eventemitter-0.2.4.tgz#f5e7c8ca7d3e46aab9ec40a292baf686a0bafaca" - integrity sha512-pd20BwL7Yt1zwDFy+8MX8F1+WCT8aQeKj0kQnTrH9WaeRETlRamVhD0JtRPmrV4GfOJ2F9CvdQkZeZhnh2TuHw== - dependencies: - async "^2.4.0" - async@1.x: version "1.5.2" resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" integrity sha512-nSVgobk4rv61R9PUSDtYt7mPVB2olxNR5RWJcAsH676/ef11bUZwvu7+RGYrYauVdDPcO519v68wRhXQtxsV9w== -async@^2.4.0: - version "2.6.4" - resolved "https://registry.yarnpkg.com/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221" - integrity sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA== - dependencies: - lodash "^4.17.14" - asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -1681,25 +1617,17 @@ at-least-node@^1.0.0: resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== -available-typed-arrays@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" - integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== - -aws-sign2@~0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" - integrity sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA== - -aws4@^1.8.0: - version "1.12.0" - resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.12.0.tgz#ce1c9d143389679e253b314241ea9aa5cec980d3" - integrity sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg== +available-typed-arrays@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846" + integrity sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ== + dependencies: + possible-typed-array-names "^1.0.0" -axe-core@^4.6.2: - version "4.6.3" - resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.6.3.tgz#fc0db6fdb65cc7a80ccf85286d91d64ababa3ece" - integrity sha512-/BQzOX780JhsxDnPpH4ZiyrJAzcd8AfzFPkv+89veFSr1rcMjuq2JDCwypKaPeB6ljHp9KjXhPpjgCvQlWYuqg== +axe-core@^4.9.1: + version "4.9.1" + resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.9.1.tgz#fcd0f4496dad09e0c899b44f6c4bb7848da912ae" + integrity sha512-QbUdXJVTpvUTHU7871ppZkdOLBeGUKBQWHkHrvN2V9IQWGMt61zf3B45BtzjxEJzYuj0JBjBZP/hmYS/R9pmAw== axios@^0.21.1: version "0.21.4" @@ -1708,16 +1636,16 @@ axios@^0.21.1: dependencies: follow-redirects "^1.14.0" -axios@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.4.0.tgz#38a7bf1224cd308de271146038b551d725f0be1f" - integrity sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA== +axios@^1.4.0, axios@^1.5.1: + version "1.7.2" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.2.tgz#b625db8a7051fbea61c35a3cbb3a1daa7b9c7621" + integrity sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw== dependencies: - follow-redirects "^1.15.0" + follow-redirects "^1.15.6" form-data "^4.0.0" proxy-from-env "^1.1.0" -axobject-query@^3.1.1: +axobject-query@~3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-3.1.1.tgz#3b6e5c6d4e43ca7ba51c5babf99d22a9c68485e1" integrity sha512-goKlv8DZrK9hUh975fnHzhNIO4jUnFCfv/dszV5VwUGDFjI6vQ2VwoyjYjYNEbBE8AH87TduWP5uyDR1D+Iteg== @@ -1730,24 +1658,12 @@ balanced-match@^1.0.0: integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== base-x@^3.0.2: - version "3.0.9" - resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.9.tgz#6349aaabb58526332de9f60995e548a53fe21320" - integrity sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ== + version "3.0.10" + resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.10.tgz#62de58653f8762b5d6f8d9fe30fa75f7b2585a75" + integrity sha512-7d0s06rR9rYaIWHkpfLIFICM/tkSVdoPC9qYAQRpxn9DdKNWNsKC0uk++akckyLq16Tx2WIinnZ6WRriAt6njQ== dependencies: safe-buffer "^5.0.1" -base64-js@^1.3.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" - integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== - -bcrypt-pbkdf@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" - integrity sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w== - dependencies: - tweetnacl "^0.14.3" - bech32@1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.4.tgz#e38c9f37bf179b8eb16ae3a772b40c356d4832e9" @@ -1758,22 +1674,10 @@ big.js@^5.2.2: resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== -bigint-crypto-utils@^3.0.23: - version "3.1.8" - resolved "https://registry.yarnpkg.com/bigint-crypto-utils/-/bigint-crypto-utils-3.1.8.tgz#e2e0f40cf45488f9d7f0e32ff84152aa73819d5d" - integrity sha512-+VMV9Laq8pXLBKKKK49nOoq9bfR3j7NNQAtbA617a4nw9bVLo8rsqkKMBgM2AJWlNX9fEIyYaYX+d0laqYV4tw== - dependencies: - bigint-mod-arith "^3.1.0" - -bigint-mod-arith@^3.1.0: - version "3.1.2" - resolved "https://registry.yarnpkg.com/bigint-mod-arith/-/bigint-mod-arith-3.1.2.tgz#658e416bc593a463d97b59766226d0a3021a76b1" - integrity sha512-nx8J8bBeiRR+NlsROFH9jHswW5HO8mgfOSqW0AmjicMMvaONDa8AO+5ViKDUUNytBPWiwfvZP4/Bj4Y3lUfvgQ== - binary-extensions@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" - integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== + version "2.3.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" + integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== blakejs@^1.1.0: version "1.2.1" @@ -1795,7 +1699,7 @@ bn.js@^4.11.0, bn.js@^4.11.8, bn.js@^4.11.9: resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== -bn.js@^5.1.2, bn.js@^5.2.0, bn.js@^5.2.1: +bn.js@^5.2.0, bn.js@^5.2.1: version "5.2.1" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== @@ -1805,6 +1709,20 @@ boolbase@^1.0.0: resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== +boxen@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/boxen/-/boxen-5.1.2.tgz#788cb686fc83c1f486dfa8a40c68fc2b831d2b50" + integrity sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ== + dependencies: + ansi-align "^3.0.0" + camelcase "^6.2.0" + chalk "^4.1.0" + cli-boxes "^2.2.1" + string-width "^4.2.2" + type-fest "^0.20.2" + widest-line "^3.1.0" + wrap-ansi "^7.0.0" + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -1820,29 +1738,19 @@ brace-expansion@^2.0.1: dependencies: balanced-match "^1.0.0" -braces@^3.0.2, braces@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== +braces@^3.0.3, braces@~3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== dependencies: - fill-range "^7.0.1" + fill-range "^7.1.1" brorand@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w== -browser-level@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/browser-level/-/browser-level-1.0.1.tgz#36e8c3183d0fe1c405239792faaab5f315871011" - integrity sha512-XECYKJ+Dbzw0lbydyQuJzwNXtOpbMSq737qxJN11sIRTErOMShvDpbzTlgju7orJKvx4epULolZAuJGLzCmWRQ== - dependencies: - abstract-level "^1.0.2" - catering "^2.1.1" - module-error "^1.0.2" - run-parallel-limit "^1.1.0" - -browser-stdout@1.3.1: +browser-stdout@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== @@ -1859,15 +1767,15 @@ browserify-aes@^1.2.0: inherits "^2.0.1" safe-buffer "^5.0.1" -browserslist@^4.14.5: - version "4.21.5" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.5.tgz#75c5dae60063ee641f977e00edd3cfb2fb7af6a7" - integrity sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w== +browserslist@^4.21.10: + version "4.23.2" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.2.tgz#244fe803641f1c19c28c48c4b6ec9736eb3d32ed" + integrity sha512-qkqSyistMYdxAcw+CzbZwlBy8AGmS/eEWs+sEV5TnLRGDOL+C5M2EnH6tlZyg0YoAxGJAFKh61En9BR941GnHA== dependencies: - caniuse-lite "^1.0.30001449" - electron-to-chromium "^1.4.284" - node-releases "^2.0.8" - update-browserslist-db "^1.0.10" + caniuse-lite "^1.0.30001640" + electron-to-chromium "^1.4.820" + node-releases "^2.0.14" + update-browserslist-db "^1.1.0" bs58@^4.0.0: version "4.0.1" @@ -1895,33 +1803,21 @@ buffer-xor@^1.0.3: resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" integrity sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ== -buffer@^6.0.3: - version "6.0.3" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" - integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== - dependencies: - base64-js "^1.3.1" - ieee754 "^1.2.1" - -busboy@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893" - integrity sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA== - dependencies: - streamsearch "^1.1.0" - bytes@3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== -call-bind@^1.0.0, call-bind@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" - integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== +call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" + integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w== dependencies: - function-bind "^1.1.1" - get-intrinsic "^1.0.2" + es-define-property "^1.0.0" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + set-function-length "^1.2.1" caller-callsite@^2.0.0: version "2.0.0" @@ -1955,35 +1851,25 @@ camel-case@^4.1.2: pascal-case "^3.1.2" tslib "^2.0.3" -camelcase@^5.0.0: - version "5.3.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" - integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== - -camelcase@^6.0.0: +camelcase@^6.0.0, camelcase@^6.2.0: version "6.3.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== -caniuse-lite@^1.0.30001449: - version "1.0.30001472" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001472.tgz#3f484885f2a2986c019dc416e65d9d62798cdd64" - integrity sha512-xWC/0+hHHQgj3/vrKYY0AAzeIUgr7L9wlELIcAvZdDUHlhL/kNxMdnQLOSOQfP8R51ZzPhmHdyMkI0MMpmxCfg== +caniuse-lite@^1.0.30001640: + version "1.0.30001643" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001643.tgz#9c004caef315de9452ab970c3da71085f8241dbd" + integrity sha512-ERgWGNleEilSrHM6iUz/zJNSQTP8Mr21wDWpdgvRwcTXGAq6jMtOUPP4dqFPTdKqZ2wKTdtB+uucZ3MRpAUSmg== caseless@^0.12.0, caseless@~0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" integrity sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw== -catering@^2.1.0, catering@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/catering/-/catering-2.1.1.tgz#66acba06ed5ee28d5286133982a927de9a04b510" - integrity sha512-K7Qy8O9p76sL3/3m7/zLKbRkyOlSZAgzEaLhyj2mXS8PsCud2Eo4hAb8aLtZqHh0QGqLcb9dlJSu6lHRVENm1w== - chai-as-promised@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/chai-as-promised/-/chai-as-promised-7.1.1.tgz#08645d825deb8696ee61725dbf590c012eb00ca0" - integrity sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA== + version "7.1.2" + resolved "https://registry.yarnpkg.com/chai-as-promised/-/chai-as-promised-7.1.2.tgz#70cd73b74afd519754161386421fb71832c6d041" + integrity sha512-aBDHZxRzYnUYuIAIPBH2s511DjlKPzXNlXSGFC8CwmroWQLfrW0LtE1nK3MAwwNhJPa9raEjNCmRoFpG0Hurdw== dependencies: check-error "^1.0.2" @@ -1993,19 +1879,19 @@ chai-bignumber@^3.0.0: integrity sha512-omxEc80jAU+pZwRmoWr3aEzeLad4JW3iBhLRQlgISvghBdIxrMT7mVAGsDz4WSyCkKowENshH2j9OABAhld7QQ== chai@^4.2.0: - version "4.3.7" - resolved "https://registry.yarnpkg.com/chai/-/chai-4.3.7.tgz#ec63f6df01829088e8bf55fca839bcd464a8ec51" - integrity sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A== + version "4.5.0" + resolved "https://registry.yarnpkg.com/chai/-/chai-4.5.0.tgz#707e49923afdd9b13a8b0b47d33d732d13812fd8" + integrity sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw== dependencies: assertion-error "^1.1.0" - check-error "^1.0.2" - deep-eql "^4.1.2" - get-func-name "^2.0.0" - loupe "^2.3.1" + check-error "^1.0.3" + deep-eql "^4.1.3" + get-func-name "^2.0.2" + loupe "^2.3.6" pathval "^1.1.1" - type-detect "^4.0.5" + type-detect "^4.1.0" -chalk@^2.0.0, chalk@^2.1.0, chalk@^2.4.2: +chalk@^2.1.0, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -2032,30 +1918,17 @@ chardet@^0.7.0: resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" integrity sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA== -check-error@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" - integrity sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA== - -chokidar@3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.3.0.tgz#12c0714668c55800f659e262d4962a97faf554a6" - integrity sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A== +check-error@^1.0.2, check-error@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.3.tgz#a6502e4312a7ee969f646e83bb3ddd56281bd694" + integrity sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg== dependencies: - anymatch "~3.1.1" - braces "~3.0.2" - glob-parent "~5.1.0" - is-binary-path "~2.1.0" - is-glob "~4.0.1" - normalize-path "~3.0.0" - readdirp "~3.2.0" - optionalDependencies: - fsevents "~2.1.1" + get-func-name "^2.0.2" -chokidar@3.5.3, chokidar@^3.4.0, chokidar@^3.5.2: - version "3.5.3" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" - integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== +chokidar@^3.4.0, chokidar@^3.5.2, chokidar@^3.5.3: + version "3.6.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" + integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== dependencies: anymatch "~3.1.2" braces "~3.0.2" @@ -2068,9 +1941,9 @@ chokidar@3.5.3, chokidar@^3.4.0, chokidar@^3.5.2: fsevents "~2.3.2" chrome-trace-event@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac" - integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg== + version "1.0.4" + resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz#05bffd7ff928465093314708c93bdfa9bd1f0f5b" + integrity sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ== ci-info@^2.0.0: version "2.0.0" @@ -2085,21 +1958,10 @@ cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: inherits "^2.0.1" safe-buffer "^5.0.1" -classic-level@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/classic-level/-/classic-level-1.2.0.tgz#2d52bdec8e7a27f534e67fdeb890abef3e643c27" - integrity sha512-qw5B31ANxSluWz9xBzklRWTUAJ1SXIdaVKTVS7HcTGKOAmExx65Wo5BUICW+YGORe2FOUaDghoI9ZDxj82QcFg== - dependencies: - abstract-level "^1.0.2" - catering "^2.1.0" - module-error "^1.0.1" - napi-macros "~2.0.0" - node-gyp-build "^4.3.0" - clean-css@^5.2.2: - version "5.3.2" - resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-5.3.2.tgz#70ecc7d4d4114921f5d298349ff86a31a9975224" - integrity sha512-JVJbM+f3d3Q704rF4bqQ5UUyTtuJ0JRKNbTKVEeujCCBoMdkEi+V+e8oktO9qGQNSvHrFTM6JZRXrUvGR1czww== + version "5.3.3" + resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-5.3.3.tgz#b330653cd3bd6b75009cc25c714cae7b93351ccd" + integrity sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg== dependencies: source-map "~0.6.0" @@ -2108,6 +1970,11 @@ clean-stack@^2.0.0: resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== +cli-boxes@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.1.tgz#ddd5035d25094fce220e9cab40a45840a440318f" + integrity sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw== + cli-cursor@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" @@ -2130,15 +1997,6 @@ cli-width@^2.0.0: resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.1.tgz#b0433d0b4e9c847ef18868a4ef16fd5fc8271c48" integrity sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw== -cliui@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" - integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA== - dependencies: - string-width "^3.1.0" - strip-ansi "^5.2.0" - wrap-ansi "^5.1.0" - cliui@^7.0.2: version "7.0.4" resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" @@ -2177,7 +2035,7 @@ colors@1.4.0, colors@^1.1.2: resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== -combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: +combined-stream@^1.0.6, combined-stream@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== @@ -2214,17 +2072,12 @@ commander@2.18.0: resolved "https://registry.yarnpkg.com/commander/-/commander-2.18.0.tgz#2bf063ddee7c7891176981a2cc798e5754bc6970" integrity sha512-6CYPa+JP2ftfRU2qkDK+UTVeQYosOg/2GbcjIcKPHfinyOLPVGXu/ovN86RP49Re5ndJK1N0kuiidFFuepc4ZQ== -commander@3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/commander/-/commander-3.0.2.tgz#6837c3fb677ad9933d1cfba42dd14d5117d6b39e" - integrity sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow== - commander@^2.19.0, commander@^2.20.0: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== -commander@^8.3.0: +commander@^8.1.0, commander@^8.3.0: version "8.3.0" resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== @@ -2271,11 +2124,6 @@ cookie@^0.4.1: resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== -core-util-is@1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" - integrity sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ== - core-util-is@~1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" @@ -2291,11 +2139,6 @@ cosmiconfig@^5.0.7: js-yaml "^3.13.1" parse-json "^4.0.0" -crc-32@^1.2.0: - version "1.2.2" - resolved "https://registry.yarnpkg.com/crc-32/-/crc-32-1.2.2.tgz#3cad35a934b8bf71f25ca524b6da51fb7eace2ff" - integrity sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ== - create-hash@^1.1.0, create-hash@^1.1.2, create-hash@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" @@ -2350,18 +2193,18 @@ cross-spawn@^7.0.2: integrity sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow== css-loader@^6.5.1: - version "6.7.3" - resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.7.3.tgz#1e8799f3ccc5874fdd55461af51137fcc5befbcd" - integrity sha512-qhOH1KlBMnZP8FzRO6YCH9UHXQhVMcEGLyNdb7Hv2cpcmJbW0YrddO+tG1ab5nT41KpHIYGsbeHqxB9xPu1pKQ== + version "6.11.0" + resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.11.0.tgz#33bae3bf6363d0a7c2cf9031c96c744ff54d85ba" + integrity sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g== dependencies: icss-utils "^5.1.0" - postcss "^8.4.19" - postcss-modules-extract-imports "^3.0.0" - postcss-modules-local-by-default "^4.0.0" - postcss-modules-scope "^3.0.0" + postcss "^8.4.33" + postcss-modules-extract-imports "^3.1.0" + postcss-modules-local-by-default "^4.0.5" + postcss-modules-scope "^3.2.0" postcss-modules-values "^4.0.0" postcss-value-parser "^4.2.0" - semver "^7.3.8" + semver "^7.5.4" css-select@^4.1.3: version "4.3.0" @@ -2385,21 +2228,41 @@ cssesc@^3.0.0: integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== csstype@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.1.tgz#841b532c45c758ee546a11d5bd7b7b473c8c30b9" - integrity sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw== + version "3.1.3" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" + integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== damerau-levenshtein@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz#b43d286ccbd36bc5b2f7ed41caf2d0aba1f8a6e7" integrity sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA== -dashdash@^1.12.0: - version "1.14.1" - resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" - integrity sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g== +data-view-buffer@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/data-view-buffer/-/data-view-buffer-1.0.1.tgz#8ea6326efec17a2e42620696e671d7d5a8bc66b2" + integrity sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA== + dependencies: + call-bind "^1.0.6" + es-errors "^1.3.0" + is-data-view "^1.0.1" + +data-view-byte-length@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz#90721ca95ff280677eb793749fce1011347669e2" + integrity sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ== + dependencies: + call-bind "^1.0.7" + es-errors "^1.3.0" + is-data-view "^1.0.1" + +data-view-byte-offset@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz#5e0bbfb4828ed2d1b9b400cd8a7d119bca0ff18a" + integrity sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA== dependencies: - assert-plus "^1.0.0" + call-bind "^1.0.6" + es-errors "^1.3.0" + is-data-view "^1.0.1" de-indent@^1.0.2: version "1.0.2" @@ -2411,17 +2274,10 @@ death@^1.1.0: resolved "https://registry.yarnpkg.com/death/-/death-1.1.0.tgz#01aa9c401edd92750514470b8266390c66c67318" integrity sha512-vsV6S4KVHvTGxbEcij7hkWRv0It+sGGWVOM67dQde/o5Xjnr+KmLjxWJii2uEObIrt1CcM9w0Yaovx+iOlIL+w== -debug@3.2.6: - version "3.2.6" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" - integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== - dependencies: - ms "^2.1.1" - -debug@4, debug@4.3.4, debug@^4.0.1, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4: - version "4.3.4" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" - integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== +debug@4, debug@^4.0.1, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3.5: + version "4.3.6" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.6.tgz#2ab2c38fbaffebf8aa95fdfe6d88438c7a13c52b" + integrity sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg== dependencies: ms "2.1.2" @@ -2432,11 +2288,6 @@ debug@^3.2.7: dependencies: ms "^2.1.1" -decamelize@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" - integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== - decamelize@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837" @@ -2447,23 +2298,24 @@ decimal.js@^10.3.1: resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23" integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA== -deep-eql@^4.0.1, deep-eql@^4.1.2: - version "4.1.3" - resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-4.1.3.tgz#7c7775513092f7df98d8df9996dd085eb668cc6d" - integrity sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw== +deep-eql@^4.0.1, deep-eql@^4.1.3: + version "4.1.4" + resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-4.1.4.tgz#d0d3912865911bb8fac5afb4e3acfa6a28dc72b7" + integrity sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg== dependencies: type-detect "^4.0.0" deep-equal@^2.0.5: - version "2.2.0" - resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-2.2.0.tgz#5caeace9c781028b9ff459f33b779346637c43e6" - integrity sha512-RdpzE0Hv4lhowpIUKKMJfeH6C1pXdtT1/it80ubgWqwI3qpuxUBpC1S4hnHg+zjnuOoDkzUtUCEEkG+XG5l3Mw== + version "2.2.3" + resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-2.2.3.tgz#af89dafb23a396c7da3e862abc0be27cf51d56e1" + integrity sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA== dependencies: - call-bind "^1.0.2" - es-get-iterator "^1.1.2" - get-intrinsic "^1.1.3" + array-buffer-byte-length "^1.0.0" + call-bind "^1.0.5" + es-get-iterator "^1.1.3" + get-intrinsic "^1.2.2" is-arguments "^1.1.1" - is-array-buffer "^3.0.1" + is-array-buffer "^3.0.2" is-date-object "^1.0.5" is-regex "^1.1.4" is-shared-array-buffer "^1.0.2" @@ -2471,11 +2323,11 @@ deep-equal@^2.0.5: object-is "^1.1.5" object-keys "^1.1.1" object.assign "^4.1.4" - regexp.prototype.flags "^1.4.3" + regexp.prototype.flags "^1.5.1" side-channel "^1.0.4" which-boxed-primitive "^1.0.2" which-collection "^1.0.1" - which-typed-array "^1.1.9" + which-typed-array "^1.1.13" deep-extend@~0.6.0: version "0.6.0" @@ -2487,11 +2339,21 @@ deep-is@^0.1.3, deep-is@~0.1.3: resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== -define-properties@^1.1.2, define-properties@^1.1.3, define-properties@^1.1.4: - version "1.2.0" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.0.tgz#52988570670c9eacedd8064f4a990f2405849bd5" - integrity sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA== +define-data-property@^1.0.1, define-data-property@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" + integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + gopd "^1.0.1" + +define-properties@^1.1.3, define-properties@^1.2.0, define-properties@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" + integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== dependencies: + define-data-property "^1.0.1" has-property-descriptors "^1.0.0" object-keys "^1.1.1" @@ -2505,33 +2367,15 @@ depd@2.0.0: resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== -detect-port@^1.3.0: - version "1.5.1" - resolved "https://registry.yarnpkg.com/detect-port/-/detect-port-1.5.1.tgz#451ca9b6eaf20451acb0799b8ab40dff7718727b" - integrity sha512-aBzdj76lueB6uUst5iAs7+0H/oOjqI5D16XUWxlWMIMROhcM0rfsNVk93zTngq1dDNpoXRr++Sus7ETAExppAQ== - dependencies: - address "^1.0.1" - debug "4" - -diff@3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" - integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== - -diff@5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b" - integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w== - diff@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== -diff@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/diff/-/diff-5.1.0.tgz#bc52d298c5ea8df9194800224445ed43ffc87e40" - integrity sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw== +diff@^5.0.0, diff@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-5.2.0.tgz#26ded047cd1179b78b9537d5ef725503ce1ae531" + integrity sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A== difflib@^0.2.4: version "0.2.4" @@ -2611,14 +2455,6 @@ dotenv@^10.0.0: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81" integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q== -ecc-jsbn@~0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" - integrity sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw== - dependencies: - jsbn "~0.1.0" - safer-buffer "^2.1.0" - editorconfig@^0.15.0: version "0.15.3" resolved "https://registry.yarnpkg.com/editorconfig/-/editorconfig-0.15.3.tgz#bef84c4e75fb8dcb0ce5cee8efd51c15999befc5" @@ -2629,12 +2465,12 @@ editorconfig@^0.15.0: semver "^5.6.0" sigmund "^1.0.1" -electron-to-chromium@^1.4.284: - version "1.4.342" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.342.tgz#3c7e199c3aa89c993df4b6f5223d6d26988f58e6" - integrity sha512-dTei3VResi5bINDENswBxhL+N0Mw5YnfWyTqO75KGsVldurEkhC9+CelJVAse8jycWyP8pv3VSj4BSyP8wTWJA== +electron-to-chromium@^1.4.820: + version "1.5.2" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.2.tgz#6126ad229ce45e781ec54ca40db0504787f23d19" + integrity sha512-kc4r3U3V3WLaaZqThjYz/Y6z8tJe+7K0bbjUVo3i+LWIypVdMx5nXCkwRe6SWbY6ILqLdc1rKcKmr3HoH7wjSQ== -elliptic@6.5.4, elliptic@^6.5.2, elliptic@^6.5.4: +elliptic@6.5.4: version "6.5.4" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== @@ -2647,6 +2483,19 @@ elliptic@6.5.4, elliptic@^6.5.2, elliptic@^6.5.4: minimalistic-assert "^1.0.1" minimalistic-crypto-utils "^1.0.1" +elliptic@^6.5.2, elliptic@^6.5.4: + version "6.5.6" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.6.tgz#ee5f7c3a00b98a2144ac84d67d01f04d438fa53e" + integrity sha512-mpzdtpeCLuS3BmE3pO3Cpp5bbjlOPY2Q0PgoF+Od1XZrHLYI28Xe3ossCmYCQt11FQKEYd9+PF8jymTvtWJSHQ== + dependencies: + bn.js "^4.11.9" + brorand "^1.1.0" + hash.js "^1.0.0" + hmac-drbg "^1.0.1" + inherits "^2.0.4" + minimalistic-assert "^1.0.1" + minimalistic-crypto-utils "^1.0.1" + emoji-regex@^7.0.1: version "7.0.3" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" @@ -2672,20 +2521,21 @@ encode-utf8@^1.0.2: resolved "https://registry.yarnpkg.com/encode-utf8/-/encode-utf8-1.0.3.tgz#f30fdd31da07fb596f281beb2f6b027851994cda" integrity sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw== -enhanced-resolve@^5.10.0: - version "5.12.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz#300e1c90228f5b570c4d35babf263f6da7155634" - integrity sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ== +enhanced-resolve@^5.17.0: + version "5.17.1" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz#67bfbbcc2f81d511be77d686a90267ef7f898a15" + integrity sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg== dependencies: graceful-fs "^4.2.4" tapable "^2.2.0" enquirer@^2.3.0, enquirer@^2.3.6: - version "2.3.6" - resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" - integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== + version "2.4.1" + resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.4.1.tgz#93334b3fbd74fc7097b224ab4a8fb7e40bf4ae56" + integrity sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ== dependencies: ansi-colors "^4.1.1" + strip-ansi "^6.0.1" entities@^2.0.0: version "2.2.0" @@ -2704,52 +2554,71 @@ error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -es-abstract@^1.19.0, es-abstract@^1.20.4: - version "1.21.2" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.21.2.tgz#a56b9695322c8a185dc25975aa3b8ec31d0e7eff" - integrity sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg== - dependencies: - array-buffer-byte-length "^1.0.0" - available-typed-arrays "^1.0.5" - call-bind "^1.0.2" - es-set-tostringtag "^2.0.1" +es-abstract@^1.17.5, es-abstract@^1.22.1, es-abstract@^1.22.3, es-abstract@^1.23.0, es-abstract@^1.23.1, es-abstract@^1.23.2, es-abstract@^1.23.3: + version "1.23.3" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.23.3.tgz#8f0c5a35cd215312573c5a27c87dfd6c881a0aa0" + integrity sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A== + dependencies: + array-buffer-byte-length "^1.0.1" + arraybuffer.prototype.slice "^1.0.3" + available-typed-arrays "^1.0.7" + call-bind "^1.0.7" + data-view-buffer "^1.0.1" + data-view-byte-length "^1.0.1" + data-view-byte-offset "^1.0.0" + es-define-property "^1.0.0" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + es-set-tostringtag "^2.0.3" es-to-primitive "^1.2.1" - function.prototype.name "^1.1.5" - get-intrinsic "^1.2.0" - get-symbol-description "^1.0.0" + function.prototype.name "^1.1.6" + get-intrinsic "^1.2.4" + get-symbol-description "^1.0.2" globalthis "^1.0.3" gopd "^1.0.1" - has "^1.0.3" - has-property-descriptors "^1.0.0" - has-proto "^1.0.1" + has-property-descriptors "^1.0.2" + has-proto "^1.0.3" has-symbols "^1.0.3" - internal-slot "^1.0.5" - is-array-buffer "^3.0.2" + hasown "^2.0.2" + internal-slot "^1.0.7" + is-array-buffer "^3.0.4" is-callable "^1.2.7" - is-negative-zero "^2.0.2" + is-data-view "^1.0.1" + is-negative-zero "^2.0.3" is-regex "^1.1.4" - is-shared-array-buffer "^1.0.2" + is-shared-array-buffer "^1.0.3" is-string "^1.0.7" - is-typed-array "^1.1.10" + is-typed-array "^1.1.13" is-weakref "^1.0.2" - object-inspect "^1.12.3" + object-inspect "^1.13.1" object-keys "^1.1.1" - object.assign "^4.1.4" - regexp.prototype.flags "^1.4.3" - safe-regex-test "^1.0.0" - string.prototype.trim "^1.2.7" - string.prototype.trimend "^1.0.6" - string.prototype.trimstart "^1.0.6" - typed-array-length "^1.0.4" + object.assign "^4.1.5" + regexp.prototype.flags "^1.5.2" + safe-array-concat "^1.1.2" + safe-regex-test "^1.0.3" + string.prototype.trim "^1.2.9" + string.prototype.trimend "^1.0.8" + string.prototype.trimstart "^1.0.8" + typed-array-buffer "^1.0.2" + typed-array-byte-length "^1.0.1" + typed-array-byte-offset "^1.0.2" + typed-array-length "^1.0.6" unbox-primitive "^1.0.2" - which-typed-array "^1.1.9" + which-typed-array "^1.1.15" -es-array-method-boxes-properly@^1.0.0: +es-define-property@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz#873f3e84418de4ee19c5be752990b2e44718d09e" - integrity sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA== + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845" + integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ== + dependencies: + get-intrinsic "^1.2.4" + +es-errors@^1.2.1, es-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== -es-get-iterator@^1.1.2: +es-get-iterator@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/es-get-iterator/-/es-get-iterator-1.1.3.tgz#3ef87523c5d464d41084b2c3c9c214f1199763d6" integrity sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw== @@ -2764,26 +2633,53 @@ es-get-iterator@^1.1.2: isarray "^2.0.5" stop-iteration-iterator "^1.0.0" -es-module-lexer@^0.9.0: - version "0.9.3" - resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-0.9.3.tgz#6f13db00cc38417137daf74366f535c8eb438f19" - integrity sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ== +es-iterator-helpers@^1.0.19: + version "1.0.19" + resolved "https://registry.yarnpkg.com/es-iterator-helpers/-/es-iterator-helpers-1.0.19.tgz#117003d0e5fec237b4b5c08aded722e0c6d50ca8" + integrity sha512-zoMwbCcH5hwUkKJkT8kDIBZSz9I6mVG//+lDCinLCGov4+r7NIy0ld8o03M0cJxl2spVf6ESYVS6/gpIfq1FFw== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.3" + es-errors "^1.3.0" + es-set-tostringtag "^2.0.3" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + globalthis "^1.0.3" + has-property-descriptors "^1.0.2" + has-proto "^1.0.3" + has-symbols "^1.0.3" + internal-slot "^1.0.7" + iterator.prototype "^1.1.2" + safe-array-concat "^1.1.2" -es-set-tostringtag@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz#338d502f6f674301d710b80c8592de8a15f09cd8" - integrity sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg== - dependencies: - get-intrinsic "^1.1.3" - has "^1.0.3" - has-tostringtag "^1.0.0" +es-module-lexer@^1.2.1: + version "1.5.4" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.5.4.tgz#a8efec3a3da991e60efa6b633a7cad6ab8d26b78" + integrity sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw== -es-shim-unscopables@^1.0.0: +es-object-atoms@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz#702e632193201e3edf8713635d083d378e510241" - integrity sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w== + resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.0.0.tgz#ddb55cd47ac2e240701260bc2a8e31ecb643d941" + integrity sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw== + dependencies: + es-errors "^1.3.0" + +es-set-tostringtag@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz#8bb60f0a440c2e4281962428438d58545af39777" + integrity sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ== + dependencies: + get-intrinsic "^1.2.4" + has-tostringtag "^1.0.2" + hasown "^2.0.1" + +es-shim-unscopables@^1.0.0, es-shim-unscopables@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz#1f6942e71ecc7835ed1c8a83006d8771a63a3763" + integrity sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw== dependencies: - has "^1.0.3" + hasown "^2.0.0" es-to-primitive@^1.2.1: version "1.2.1" @@ -2794,22 +2690,22 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" -escalade@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" - integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== +escalade@^3.1.1, escalade@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.2.tgz#54076e9ab29ea5bf3d8f1ed62acffbb88272df27" + integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA== escape-latex@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/escape-latex/-/escape-latex-1.2.0.tgz#07c03818cf7dac250cce517f4fda1b001ef2bca1" integrity sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw== -escape-string-regexp@1.0.5, escape-string-regexp@^1.0.5: +escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== -escape-string-regexp@4.0.0, escape-string-regexp@^4.0.0: +escape-string-regexp@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== @@ -2846,80 +2742,82 @@ eslint-config-airbnb@^19.0.4: object.entries "^1.1.5" eslint-config-prettier@^8.6.0: - version "8.8.0" - resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.8.0.tgz#bfda738d412adc917fd7b038857110efe98c9348" - integrity sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA== + version "8.10.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.10.0.tgz#3a06a662130807e2502fc3ff8b4143d8a0658e11" + integrity sha512-SM8AMJdeQqRYT9O9zguiruQZaN7+z+E4eAP9oiLNGKMtomwaB1E9dcgUD6ZAn/eQAb52USbvezbiljfZUhbJcg== -eslint-import-resolver-node@^0.3.7: - version "0.3.7" - resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz#83b375187d412324a1963d84fa664377a23eb4d7" - integrity sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA== +eslint-import-resolver-node@^0.3.9: + version "0.3.9" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz#d4eaac52b8a2e7c3cd1903eb00f7e053356118ac" + integrity sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g== dependencies: debug "^3.2.7" - is-core-module "^2.11.0" - resolve "^1.22.1" + is-core-module "^2.13.0" + resolve "^1.22.4" -eslint-module-utils@^2.7.4: - version "2.7.4" - resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz#4f3e41116aaf13a20792261e61d3a2e7e0583974" - integrity sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA== +eslint-module-utils@^2.8.0: + version "2.8.1" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.8.1.tgz#52f2404300c3bd33deece9d7372fb337cc1d7c34" + integrity sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q== dependencies: debug "^3.2.7" eslint-plugin-chai-friendly@^0.7.2: - version "0.7.2" - resolved "https://registry.yarnpkg.com/eslint-plugin-chai-friendly/-/eslint-plugin-chai-friendly-0.7.2.tgz#0ebfbb2c1244f5de2997f3963d155758234f2b0f" - integrity sha512-LOIfGx5sZZ5FwM1shr2GlYAWV9Omdi+1/3byuVagvQNoGUuU0iHhp7AfjA1uR+4dJ4Isfb4+FwBJgQajIw9iAg== + version "0.7.4" + resolved "https://registry.yarnpkg.com/eslint-plugin-chai-friendly/-/eslint-plugin-chai-friendly-0.7.4.tgz#eaf222b848673ef8a00b8e507f7c6fd83d036bf2" + integrity sha512-PGPjJ8diYgX1mjLxGJqRop2rrGwZRKImoEOwUOgoIhg0p80MkTaqvmFLe5TF7/iagZHggasvIfQlUyHIhK/PYg== eslint-plugin-import@^2.27.5: - version "2.27.5" - resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz#876a6d03f52608a3e5bb439c2550588e51dd6c65" - integrity sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow== - dependencies: - array-includes "^3.1.6" - array.prototype.flat "^1.3.1" - array.prototype.flatmap "^1.3.1" + version "2.29.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz#d45b37b5ef5901d639c15270d74d46d161150643" + integrity sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw== + dependencies: + array-includes "^3.1.7" + array.prototype.findlastindex "^1.2.3" + array.prototype.flat "^1.3.2" + array.prototype.flatmap "^1.3.2" debug "^3.2.7" doctrine "^2.1.0" - eslint-import-resolver-node "^0.3.7" - eslint-module-utils "^2.7.4" - has "^1.0.3" - is-core-module "^2.11.0" + eslint-import-resolver-node "^0.3.9" + eslint-module-utils "^2.8.0" + hasown "^2.0.0" + is-core-module "^2.13.1" is-glob "^4.0.3" minimatch "^3.1.2" - object.values "^1.1.6" - resolve "^1.22.1" - semver "^6.3.0" - tsconfig-paths "^3.14.1" + object.fromentries "^2.0.7" + object.groupby "^1.0.1" + object.values "^1.1.7" + semver "^6.3.1" + tsconfig-paths "^3.15.0" eslint-plugin-jest@^27.2.1: - version "27.2.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-27.2.1.tgz#b85b4adf41c682ea29f1f01c8b11ccc39b5c672c" - integrity sha512-l067Uxx7ZT8cO9NJuf+eJHvt6bqJyz2Z29wykyEdz/OtmcELQl2MQGQLX8J94O1cSJWAwUSEvCjwjA7KEK3Hmg== + version "27.9.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-27.9.0.tgz#7c98a33605e1d8b8442ace092b60e9919730000b" + integrity sha512-QIT7FH7fNmd9n4se7FFKHbsLKGQiw885Ds6Y/sxKgCZ6natwCsXdgPOADnYVxN2QrRweF0FZWbJ6S7Rsn7llug== dependencies: "@typescript-eslint/utils" "^5.10.0" eslint-plugin-jsx-a11y@^6.7.1: - version "6.7.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.7.1.tgz#fca5e02d115f48c9a597a6894d5bcec2f7a76976" - integrity sha512-63Bog4iIethyo8smBklORknVjB0T2dwB8Mr/hIC+fBS0uyHdYYpzM/Ed+YC8VxTjlXHEWFOdmgwcDn1U2L9VCA== - dependencies: - "@babel/runtime" "^7.20.7" - aria-query "^5.1.3" - array-includes "^3.1.6" - array.prototype.flatmap "^1.3.1" - ast-types-flow "^0.0.7" - axe-core "^4.6.2" - axobject-query "^3.1.1" + version "6.9.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.9.0.tgz#67ab8ff460d4d3d6a0b4a570e9c1670a0a8245c8" + integrity sha512-nOFOCaJG2pYqORjK19lqPqxMO/JpvdCZdPtNdxY3kvom3jTvkAbOvQvD8wuD0G8BYR0IGAGYDlzqWJOh/ybn2g== + dependencies: + aria-query "~5.1.3" + array-includes "^3.1.8" + array.prototype.flatmap "^1.3.2" + ast-types-flow "^0.0.8" + axe-core "^4.9.1" + axobject-query "~3.1.1" damerau-levenshtein "^1.0.8" emoji-regex "^9.2.2" - has "^1.0.3" - jsx-ast-utils "^3.3.3" - language-tags "=1.0.5" + es-iterator-helpers "^1.0.19" + hasown "^2.0.2" + jsx-ast-utils "^3.3.5" + language-tags "^1.0.9" minimatch "^3.1.2" - object.entries "^1.1.6" - object.fromentries "^2.0.6" - semver "^6.3.0" + object.fromentries "^2.0.8" + safe-regex-test "^1.0.3" + string.prototype.includes "^2.0.0" eslint-plugin-no-only-tests@^3.1.0: version "3.1.0" @@ -2968,10 +2866,10 @@ eslint-scope@^4.0.3: esrecurse "^4.1.0" estraverse "^4.1.1" -eslint-scope@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.1.1.tgz#fff34894c2f65e5226d3041ac480b4513a163642" - integrity sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw== +eslint-scope@^7.2.2: + version "7.2.2" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" + integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== dependencies: esrecurse "^4.3.0" estraverse "^5.2.0" @@ -2988,10 +2886,10 @@ eslint-visitor-keys@^1.0.0, eslint-visitor-keys@^1.1.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== -eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz#c7f0f956124ce677047ddbc192a68f999454dedc" - integrity sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ== +eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== eslint@^5.6.0: version "5.16.0" @@ -3036,26 +2934,27 @@ eslint@^5.6.0: text-table "^0.2.0" eslint@^8.32.0: - version "8.37.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.37.0.tgz#1f660ef2ce49a0bfdec0b0d698e0b8b627287412" - integrity sha512-NU3Ps9nI05GUoVMxcZx1J8CNR6xOvUT4jAUMH5+z8lpp3aEdPVCImKw6PWG4PY+Vfkpr+jvMpxs/qoE7wq0sPw== + version "8.57.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.0.tgz#c786a6fd0e0b68941aaf624596fb987089195668" + integrity sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ== dependencies: "@eslint-community/eslint-utils" "^4.2.0" - "@eslint-community/regexpp" "^4.4.0" - "@eslint/eslintrc" "^2.0.2" - "@eslint/js" "8.37.0" - "@humanwhocodes/config-array" "^0.11.8" + "@eslint-community/regexpp" "^4.6.1" + "@eslint/eslintrc" "^2.1.4" + "@eslint/js" "8.57.0" + "@humanwhocodes/config-array" "^0.11.14" "@humanwhocodes/module-importer" "^1.0.1" "@nodelib/fs.walk" "^1.2.8" - ajv "^6.10.0" + "@ungap/structured-clone" "^1.2.0" + ajv "^6.12.4" chalk "^4.0.0" cross-spawn "^7.0.2" debug "^4.3.2" doctrine "^3.0.0" escape-string-regexp "^4.0.0" - eslint-scope "^7.1.1" - eslint-visitor-keys "^3.4.0" - espree "^9.5.1" + eslint-scope "^7.2.2" + eslint-visitor-keys "^3.4.3" + espree "^9.6.1" esquery "^1.4.2" esutils "^2.0.2" fast-deep-equal "^3.1.3" @@ -3063,22 +2962,19 @@ eslint@^8.32.0: find-up "^5.0.0" glob-parent "^6.0.2" globals "^13.19.0" - grapheme-splitter "^1.0.4" + graphemer "^1.4.0" ignore "^5.2.0" - import-fresh "^3.0.0" imurmurhash "^0.1.4" is-glob "^4.0.0" is-path-inside "^3.0.3" - js-sdsl "^4.1.4" js-yaml "^4.1.0" json-stable-stringify-without-jsonify "^1.0.1" levn "^0.4.1" lodash.merge "^4.6.2" minimatch "^3.1.2" natural-compare "^1.4.0" - optionator "^0.9.1" + optionator "^0.9.3" strip-ansi "^6.0.1" - strip-json-comments "^3.1.0" text-table "^0.2.0" espree@^5.0.1: @@ -3099,14 +2995,14 @@ espree@^6.1.2: acorn-jsx "^5.2.0" eslint-visitor-keys "^1.1.0" -espree@^9.5.1: - version "9.5.1" - resolved "https://registry.yarnpkg.com/espree/-/espree-9.5.1.tgz#4f26a4d5f18905bf4f2e0bd99002aab807e96dd4" - integrity sha512-5yxtHSZXRSW5pvv3hAlXM5+/Oswi1AUFqBmbibKb5s6bp3rGIDkyXU6xCoyuuLhijr4SFwPrXRoZjz0AZDN9tg== +espree@^9.6.0, espree@^9.6.1: + version "9.6.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" + integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== dependencies: - acorn "^8.8.0" + acorn "^8.9.0" acorn-jsx "^5.3.2" - eslint-visitor-keys "^3.4.0" + eslint-visitor-keys "^3.4.1" esprima@2.7.x, esprima@^2.7.1: version "2.7.3" @@ -3119,9 +3015,9 @@ esprima@^4.0.0: integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== esquery@^1.0.1, esquery@^1.4.2: - version "1.5.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b" - integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== + version "1.6.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7" + integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== dependencies: estraverse "^5.1.0" @@ -3153,32 +3049,30 @@ esutils@^2.0.2: integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== eth-gas-reporter@^0.2.25: - version "0.2.25" - resolved "https://registry.yarnpkg.com/eth-gas-reporter/-/eth-gas-reporter-0.2.25.tgz#546dfa946c1acee93cb1a94c2a1162292d6ff566" - integrity sha512-1fRgyE4xUB8SoqLgN3eDfpDfwEfRxh2Sz1b7wzFbyQA+9TekMmvSjjoRu9SKcSVyK+vLkLIsVbJDsTWjw195OQ== + version "0.2.27" + resolved "https://registry.yarnpkg.com/eth-gas-reporter/-/eth-gas-reporter-0.2.27.tgz#928de8548a674ed64c7ba0bf5795e63079150d4e" + integrity sha512-femhvoAM7wL0GcI8ozTdxfuBtBFJ9qsyIAsmKVjlWAHUbdnnXHt+lKzz/kmldM5lA9jLuNHGwuIxorNpLbR1Zw== dependencies: - "@ethersproject/abi" "^5.0.0-beta.146" "@solidity-parser/parser" "^0.14.0" + axios "^1.5.1" cli-table3 "^0.5.0" colors "1.4.0" ethereum-cryptography "^1.0.3" - ethers "^4.0.40" + ethers "^5.7.2" fs-readdir-recursive "^1.1.0" lodash "^4.17.14" markdown-table "^1.1.3" - mocha "^7.1.1" + mocha "^10.2.0" req-cwd "^2.0.0" - request "^2.88.0" - request-promise-native "^1.0.5" sha1 "^1.1.1" sync-request "^6.0.0" ethereum-bloom-filters@^1.0.6: - version "1.0.10" - resolved "https://registry.yarnpkg.com/ethereum-bloom-filters/-/ethereum-bloom-filters-1.0.10.tgz#3ca07f4aed698e75bd134584850260246a5fed8a" - integrity sha512-rxJ5OFN3RwjQxDcFP2Z5+Q9ho4eIdEmSc2ht0fCu8Se9nbXjZ7/031uXoUYJ87KHCOdVeiUuwSnoS7hmYAGVHA== + version "1.2.0" + resolved "https://registry.yarnpkg.com/ethereum-bloom-filters/-/ethereum-bloom-filters-1.2.0.tgz#8294f074c1a6cbd32c39d2cc77ce86ff14797dab" + integrity sha512-28hyiE7HVsWubqhpVLVmZXFd4ITeHi+BUu05o9isf0GUpMtzBUi+8/gFrGaGYzvGAJQmJ3JKj77Mk9G98T84rA== dependencies: - js-sha3 "^0.8.0" + "@noble/hashes" "^1.4.0" ethereum-cryptography@0.1.3, ethereum-cryptography@^0.1.3: version "0.1.3" @@ -3201,7 +3095,7 @@ ethereum-cryptography@0.1.3, ethereum-cryptography@^0.1.3: secp256k1 "^4.0.1" setimmediate "^1.0.5" -ethereum-cryptography@^1.0.3, ethereum-cryptography@^1.1.2: +ethereum-cryptography@^1.0.3: version "1.2.0" resolved "https://registry.yarnpkg.com/ethereum-cryptography/-/ethereum-cryptography-1.2.0.tgz#5ccfa183e85fdaf9f9b299a79430c044268c9b3a" integrity sha512-6yFQC9b5ug6/17CQpCyE3k9eKBMdhyVjzUy1WkiuY/E4vj/SXDBbCw8QEIaXqf0Mf2SnY6RmpDcwlUmBSS0EJw== @@ -3211,6 +3105,16 @@ ethereum-cryptography@^1.0.3, ethereum-cryptography@^1.1.2: "@scure/bip32" "1.1.5" "@scure/bip39" "1.1.1" +ethereum-cryptography@^2.0.0, ethereum-cryptography@^2.1.2: + version "2.2.1" + resolved "https://registry.yarnpkg.com/ethereum-cryptography/-/ethereum-cryptography-2.2.1.tgz#58f2810f8e020aecb97de8c8c76147600b0b8ccf" + integrity sha512-r/W8lkHSiTLxUxW8Rf3u4HGB0xQweG2RyETjywylKZSzLWoWAijRz8WCuOtJ6wah+avllXBqZuk29HCCvhEIRg== + dependencies: + "@noble/curves" "1.4.2" + "@noble/hashes" "1.4.0" + "@scure/bip32" "1.4.0" + "@scure/bip39" "1.3.0" + ethereumjs-abi@^0.6.8, "ethereumjs-abi@https://registry.npmjs.org/ethereumjs-abi/-/ethereumjs-abi-0.6.8.tgz": version "0.6.8" resolved "https://registry.npmjs.org/ethereumjs-abi/-/ethereumjs-abi-0.6.8.tgz#71bc152db099f70e62f108b7cdfca1b362c6fcae" @@ -3231,33 +3135,7 @@ ethereumjs-util@^6.0.0, ethereumjs-util@^6.2.1: ethjs-util "0.1.6" rlp "^2.2.3" -ethereumjs-util@^7.1.0: - version "7.1.5" - resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz#9ecf04861e4fbbeed7465ece5f23317ad1129181" - integrity sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg== - dependencies: - "@types/bn.js" "^5.1.0" - bn.js "^5.1.2" - create-hash "^1.1.2" - ethereum-cryptography "^0.1.3" - rlp "^2.2.4" - -ethers@^4.0.40: - version "4.0.49" - resolved "https://registry.yarnpkg.com/ethers/-/ethers-4.0.49.tgz#0eb0e9161a0c8b4761be547396bbe2fb121a8894" - integrity sha512-kPltTvWiyu+OktYy1IStSO16i2e7cS9D9OxZ81q2UUaiNPVrm/RTcbxamCXF9VUSKzJIdJV68EAIhTEVBalRWg== - dependencies: - aes-js "3.0.0" - bn.js "^4.11.9" - elliptic "6.5.4" - hash.js "1.1.3" - js-sha3 "0.5.7" - scrypt-js "2.0.4" - setimmediate "1.0.4" - uuid "2.0.1" - xmlhttprequest "1.8.0" - -ethers@^5.5.3, ethers@^5.7.2: +ethers@^5.7.0, ethers@^5.7.2: version "5.7.2" resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.7.2.tgz#3a7deeabbb8c030d4126b24f84e525466145872e" integrity sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg== @@ -3309,11 +3187,6 @@ ethjs-util@0.1.6, ethjs-util@^0.1.6: is-hex-prefixed "1.0.0" strip-hex-prefix "1.0.0" -event-target-shim@^5.0.0: - version "5.0.1" - resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" - integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== - events@^3.2.0: version "3.3.0" resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" @@ -3335,11 +3208,6 @@ evp_bytestokey@^1.0.3: md5.js "^1.3.4" safe-buffer "^5.1.1" -extend@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" - integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== - external-editor@^3.0.3: version "3.1.0" resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" @@ -3349,30 +3217,20 @@ external-editor@^3.0.3: iconv-lite "^0.4.24" tmp "^0.0.33" -extsprintf@1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" - integrity sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g== - -extsprintf@^1.2.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.1.tgz#8d172c064867f235c0c84a596806d279bf4bcc07" - integrity sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA== - fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== fast-diff@^1.1.2: - version "1.2.0" - resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" - integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== + version "1.3.0" + resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0" + integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw== fast-glob@^3.0.3, fast-glob@^3.2.9: - version "3.2.12" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.12.tgz#7f39ec99c2e6ab030337142da9e0c18f37afae80" - integrity sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w== + version "3.3.2" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" + integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== dependencies: "@nodelib/fs.stat" "^2.0.2" "@nodelib/fs.walk" "^1.2.3" @@ -3391,9 +3249,9 @@ fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6: integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== fastq@^1.6.0: - version "1.15.0" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.15.0.tgz#d04d07c6a2a68fe4599fea8d2e103a937fae6b3a" - integrity sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw== + version "1.17.1" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47" + integrity sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w== dependencies: reusify "^1.0.4" @@ -3418,10 +3276,10 @@ file-entry-cache@^6.0.1: dependencies: flat-cache "^3.0.4" -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== dependencies: to-regex-range "^5.0.1" @@ -3432,14 +3290,14 @@ find-replace@^3.0.0: dependencies: array-back "^3.0.1" -find-up@3.0.0, find-up@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" - integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== +find-up@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" + integrity sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ== dependencies: - locate-path "^3.0.0" + locate-path "^2.0.0" -find-up@5.0.0, find-up@^5.0.0: +find-up@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== @@ -3447,13 +3305,6 @@ find-up@5.0.0, find-up@^5.0.0: locate-path "^6.0.0" path-exists "^4.0.0" -find-up@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" - integrity sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ== - dependencies: - locate-path "^2.0.0" - flat-cache@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0" @@ -3464,20 +3315,14 @@ flat-cache@^2.0.1: write "1.0.3" flat-cache@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" - integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== + version "3.2.0" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.2.0.tgz#2c0c2d5040c99b1632771a9d105725c0115363ee" + integrity sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw== dependencies: - flatted "^3.1.0" + flatted "^3.2.9" + keyv "^4.5.3" rimraf "^3.0.2" -flat@^4.1.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/flat/-/flat-4.1.1.tgz#a392059cc382881ff98642f5da4dde0a959f309b" - integrity sha512-FmTtBsHskrU6FJ2VxCnsDb84wu9zhmO3cUX2kGFb5tuwhfXxGciiT0oRY+cck35QmG+NmGh5eLz6lLCpWTqwpA== - dependencies: - is-buffer "~2.0.3" - flat@^5.0.2: version "5.0.2" resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" @@ -3488,10 +3333,10 @@ flatted@^2.0.0: resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138" integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA== -flatted@^3.1.0: - version "3.2.7" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787" - integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== +flatted@^3.2.9: + version "3.3.1" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a" + integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw== fmix@^0.1.0: version "0.1.0" @@ -3500,10 +3345,10 @@ fmix@^0.1.0: dependencies: imul "^1.0.0" -follow-redirects@^1.12.1, follow-redirects@^1.14.0, follow-redirects@^1.15.0: - version "1.15.2" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" - integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== +follow-redirects@^1.12.1, follow-redirects@^1.14.0, follow-redirects@^1.15.6: + version "1.15.6" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b" + integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== for-each@^0.3.3: version "0.3.3" @@ -3512,11 +3357,6 @@ for-each@^0.3.3: dependencies: is-callable "^1.1.3" -forever-agent@~0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" - integrity sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw== - form-data@^2.2.0: version "2.5.1" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4" @@ -3535,15 +3375,6 @@ form-data@^4.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" -form-data@~2.3.2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" - integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.6" - mime-types "^2.1.12" - fp-ts@1.19.3: version "1.19.3" resolved "https://registry.yarnpkg.com/fp-ts/-/fp-ts-1.19.3.tgz#261a60d1088fbff01f91256f91d21d0caaaaa96f" @@ -3555,26 +3386,15 @@ fp-ts@^1.0.0: integrity sha512-wDNqTimnzs8QqpldiId9OavWK2NptormjXnRJTQecNjzwfyp6P/8s/zG8e4h3ja3oqkKaY72UlTjQYt/1yXf9A== fraction.js@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.2.0.tgz#448e5109a313a3527f5a3ab2119ec4cf0e0e2950" - integrity sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA== + version "4.3.7" + resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.7.tgz#06ca0085157e42fda7f9e726e79fefc4068840f7" + integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew== from-exponential@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/from-exponential/-/from-exponential-1.1.1.tgz#41caff748d22e9c195713802cdac31acbe4b1b83" integrity sha512-VBE7f5OVnYwdgB3LHa+Qo29h8qVpxhVO9Trlc+AWm+/XNAgks1tAwMFHb33mjeiof77GglsJzeYF7OqXrROP/A== -fs-extra@^0.30.0: - version "0.30.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-0.30.0.tgz#f233ffcc08d4da7d432daa449776989db1df93f0" - integrity sha512-UvSPKyhMn6LEd/WpUaV9C9t3zATuqoqfWc3QdPhPLb58prN9tqYPlPWi8Krxi44loBoUzlobqZ3+8tGpxxSzwA== - dependencies: - graceful-fs "^4.1.2" - jsonfile "^2.1.0" - klaw "^1.0.0" - path-is-absolute "^1.0.0" - rimraf "^2.2.8" - fs-extra@^10.0.0, fs-extra@^10.1.0: version "10.1.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf" @@ -3622,79 +3442,70 @@ fs.realpath@^1.0.0: resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== -fsevents@~2.1.1: - version "2.1.3" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e" - integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ== - fsevents@~2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" - integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== -function-bind@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" - integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== -function.prototype.name@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.5.tgz#cce0505fe1ffb80503e6f9e46cc64e46a12a9621" - integrity sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA== +function.prototype.name@^1.1.5, function.prototype.name@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.6.tgz#cdf315b7d90ee77a4c6ee216c3c3362da07533fd" + integrity sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg== dependencies: call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.19.0" - functions-have-names "^1.2.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + functions-have-names "^1.2.3" functional-red-black-tree@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" integrity sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g== -functions-have-names@^1.2.2: +functions-have-names@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== -get-caller-file@^2.0.1, get-caller-file@^2.0.5: +get-caller-file@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== -get-func-name@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41" - integrity sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig== +get-func-name@^2.0.1, get-func-name@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.2.tgz#0d7cf20cd13fda808669ffa88f4ffc7a3943fc41" + integrity sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ== -get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.0.tgz#7ad1dc0535f3a2904bba075772763e5051f6d05f" - integrity sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q== +get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.2, get-intrinsic@^1.2.3, get-intrinsic@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd" + integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== dependencies: - function-bind "^1.1.1" - has "^1.0.3" + es-errors "^1.3.0" + function-bind "^1.1.2" + has-proto "^1.0.1" has-symbols "^1.0.3" + hasown "^2.0.0" get-port@^3.1.0: version "3.2.0" resolved "https://registry.yarnpkg.com/get-port/-/get-port-3.2.0.tgz#dd7ce7de187c06c8bf353796ac71e099f0980ebc" integrity sha512-x5UJKlgeUiNT8nyo/AcnwLnZuZNcSjSw0kogRB+Whd1fjjFq4B1hySFxSFWWSn4mIBzg3sRNUDFYc4g5gjPoLg== -get-symbol-description@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6" - integrity sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw== - dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.1.1" - -getpass@^0.1.1: - version "0.1.7" - resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" - integrity sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng== +get-symbol-description@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.2.tgz#533744d5aa20aca4e079c8e5daf7fd44202821f5" + integrity sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg== dependencies: - assert-plus "^1.0.0" + call-bind "^1.0.5" + es-errors "^1.3.0" + get-intrinsic "^1.2.4" ghost-testrpc@^0.0.2: version "0.0.2" @@ -3704,7 +3515,7 @@ ghost-testrpc@^0.0.2: chalk "^2.4.2" node-emoji "^1.10.0" -glob-parent@^5.1.2, glob-parent@~5.1.0, glob-parent@~5.1.2: +glob-parent@^5.1.2, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== @@ -3723,18 +3534,6 @@ glob-to-regexp@^0.4.1: resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== -glob@7.1.3: - version "7.1.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" - integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - glob@7.1.7: version "7.1.7" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" @@ -3782,6 +3581,17 @@ glob@^7.0.0, glob@^7.1.2, glob@^7.1.3: once "^1.3.0" path-is-absolute "^1.0.0" +glob@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" + integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^5.0.1" + once "^1.3.0" + global-modules@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780" @@ -3804,18 +3614,19 @@ globals@^11.7.0: integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== globals@^13.19.0: - version "13.20.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.20.0.tgz#ea276a1e508ffd4f1612888f9d1bad1e2717bf82" - integrity sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ== + version "13.24.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171" + integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ== dependencies: type-fest "^0.20.2" globalthis@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.3.tgz#5852882a52b80dc301b0660273e1ed082f0b6ccf" - integrity sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA== + version "1.0.4" + resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.4.tgz#7430ed3a975d97bfb59bcce41f5cabbafa651236" + integrity sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ== dependencies: - define-properties "^1.1.3" + define-properties "^1.2.1" + gopd "^1.0.1" globby@^10.0.1: version "10.0.2" @@ -3850,69 +3661,62 @@ gopd@^1.0.1: dependencies: get-intrinsic "^1.1.3" -graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.9: +graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.11, graceful-fs@^4.2.4: version "4.2.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== -grapheme-splitter@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e" - integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ== - -growl@1.10.5: - version "1.10.5" - resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" - integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA== +graphemer@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" + integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== handlebars@^4.0.1: - version "4.7.7" - resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.7.tgz#9ce33416aad02dbd6c8fafa8240d5d98004945a1" - integrity sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA== + version "4.7.8" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.8.tgz#41c42c18b1be2365439188c77c6afae71c0cd9e9" + integrity sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ== dependencies: minimist "^1.2.5" - neo-async "^2.6.0" + neo-async "^2.6.2" source-map "^0.6.1" wordwrap "^1.0.0" optionalDependencies: uglify-js "^3.1.4" -har-schema@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" - integrity sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q== - -har-validator@~5.1.3: - version "5.1.5" - resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd" - integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w== - dependencies: - ajv "^6.12.3" - har-schema "^2.0.0" - hardhat-deploy-ethers@^0.3.0-beta.13: version "0.3.0-beta.13" resolved "https://registry.yarnpkg.com/hardhat-deploy-ethers/-/hardhat-deploy-ethers-0.3.0-beta.13.tgz#b96086ff768ddf69928984d5eb0a8d78cfca9366" integrity sha512-PdWVcKB9coqWV1L7JTpfXRCI91Cgwsm7KLmBcwZ8f0COSm1xtABHZTyz3fvF6p42cTnz1VM0QnfDvMFlIRkSNw== hardhat-deploy@^0.11.15: - version "0.11.25" - resolved "https://registry.yarnpkg.com/hardhat-deploy/-/hardhat-deploy-0.11.25.tgz#bd6f2310ad9232a5d73f6e5dfff4112220a392e8" - integrity sha512-ppSgrVE9A13YgTmf2PQGoyIs9o/jgJOMORrUP/rblU5K8mQ2YHWlPvkzZmP4h+SBW+tNmlnvSrf5K5DmMmExhw== + version "0.11.45" + resolved "https://registry.yarnpkg.com/hardhat-deploy/-/hardhat-deploy-0.11.45.tgz#bed86118175a38a03bb58aba2ce1ed5e80a20bc8" + integrity sha512-aC8UNaq3JcORnEUIwV945iJuvBwi65tjHVDU3v6mOcqik7WAzHVCJ7cwmkkipsHrWysrB5YvGF1q9S1vIph83w== dependencies: + "@ethersproject/abi" "^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/contracts" "^5.7.0" + "@ethersproject/providers" "^5.7.2" + "@ethersproject/solidity" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + "@ethersproject/wallet" "^5.7.0" "@types/qs" "^6.9.7" axios "^0.21.1" chalk "^4.1.2" chokidar "^3.5.2" debug "^4.3.2" enquirer "^2.3.6" - ethers "^5.5.3" + ethers "^5.7.0" form-data "^4.0.0" fs-extra "^10.0.0" match-all "^1.2.6" murmur-128 "^0.2.1" qs "^6.9.4" - zksync-web3 "^0.8.1" + zksync-web3 "^0.14.3" hardhat-docgen@^1.3.0: version "1.3.0" @@ -3928,39 +3732,33 @@ hardhat-docgen@^1.3.0: webpack "^5.65.0" hardhat-gas-reporter@^1.0.9: - version "1.0.9" - resolved "https://registry.yarnpkg.com/hardhat-gas-reporter/-/hardhat-gas-reporter-1.0.9.tgz#9a2afb354bc3b6346aab55b1c02ca556d0e16450" - integrity sha512-INN26G3EW43adGKBNzYWOlI3+rlLnasXTwW79YNnUhXPDa+yHESgt639dJEs37gCjhkbNKcRRJnomXEuMFBXJg== + version "1.0.10" + resolved "https://registry.yarnpkg.com/hardhat-gas-reporter/-/hardhat-gas-reporter-1.0.10.tgz#ebe5bda5334b5def312747580cd923c2b09aef1b" + integrity sha512-02N4+So/fZrzJ88ci54GqwVA3Zrf0C9duuTyGt0CFRIh/CdNwbnTgkXkRfojOMLBQ+6t+lBIkgbsOtqMvNwikA== dependencies: array-uniq "1.0.3" eth-gas-reporter "^0.2.25" sha1 "^1.1.1" -hardhat@^2.11.2: - version "2.13.0" - resolved "https://registry.yarnpkg.com/hardhat/-/hardhat-2.13.0.tgz#d52a0ec9b733a651687e5b1c1b0ee9a11a30f3d0" - integrity sha512-ZlzBOLML1QGlm6JWyVAG8lVTEAoOaVm1in/RU2zoGAnYEoD1Rp4T+ZMvrLNhHaaeS9hfjJ1gJUBfiDr4cx+htQ== +hardhat@^2.14.2: + version "2.22.6" + resolved "https://registry.yarnpkg.com/hardhat/-/hardhat-2.22.6.tgz#d73caece246cd8219a1815554dabc31d400fa035" + integrity sha512-abFEnd9QACwEtSvZZGSmzvw7N3zhQN1cDKz5SLHAupfG24qTHofCjqvD5kT5Wwsq5XOL0ON1Mq5rr4v0XX5ciw== dependencies: "@ethersproject/abi" "^5.1.2" "@metamask/eth-sig-util" "^4.0.0" - "@nomicfoundation/ethereumjs-block" "^4.0.0" - "@nomicfoundation/ethereumjs-blockchain" "^6.0.0" - "@nomicfoundation/ethereumjs-common" "^3.0.0" - "@nomicfoundation/ethereumjs-evm" "^1.0.0" - "@nomicfoundation/ethereumjs-rlp" "^4.0.0" - "@nomicfoundation/ethereumjs-statemanager" "^1.0.0" - "@nomicfoundation/ethereumjs-trie" "^5.0.0" - "@nomicfoundation/ethereumjs-tx" "^4.0.0" - "@nomicfoundation/ethereumjs-util" "^8.0.0" - "@nomicfoundation/ethereumjs-vm" "^6.0.0" + "@nomicfoundation/edr" "^0.4.1" + "@nomicfoundation/ethereumjs-common" "4.0.4" + "@nomicfoundation/ethereumjs-tx" "5.0.4" + "@nomicfoundation/ethereumjs-util" "9.0.4" "@nomicfoundation/solidity-analyzer" "^0.1.0" "@sentry/node" "^5.18.1" "@types/bn.js" "^5.1.0" "@types/lru-cache" "^5.1.0" - abort-controller "^3.0.0" adm-zip "^0.4.16" aggregate-error "^3.0.0" ansi-escapes "^4.3.0" + boxen "^5.1.2" chalk "^2.4.2" chokidar "^3.4.0" ci-info "^2.0.0" @@ -3980,11 +3778,10 @@ hardhat@^2.11.2: mnemonist "^0.38.0" mocha "^10.0.0" p-map "^4.0.0" - qs "^6.7.0" raw-body "^2.4.1" resolve "1.17.0" semver "^6.3.0" - solc "0.7.3" + solc "0.8.26" source-map-support "^0.5.13" stacktrace-parser "^0.1.10" tsort "0.0.1" @@ -4012,36 +3809,29 @@ has-flag@^4.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== -has-property-descriptors@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz#610708600606d36961ed04c196193b6a607fa861" - integrity sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ== +has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" + integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== dependencies: - get-intrinsic "^1.1.1" + es-define-property "^1.0.0" -has-proto@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0" - integrity sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg== +has-proto@^1.0.1, has-proto@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.3.tgz#b31ddfe9b0e6e9914536a6ab286426d0214f77fd" + integrity sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q== -has-symbols@^1.0.0, has-symbols@^1.0.2, has-symbols@^1.0.3: +has-symbols@^1.0.2, has-symbols@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== -has-tostringtag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25" - integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ== - dependencies: - has-symbols "^1.0.2" - -has@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" - integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== +has-tostringtag@^1.0.0, has-tostringtag@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" + integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== dependencies: - function-bind "^1.1.1" + has-symbols "^1.0.3" hash-base@^3.0.0: version "3.1.0" @@ -4057,14 +3847,6 @@ hash-sum@^1.0.2: resolved "https://registry.yarnpkg.com/hash-sum/-/hash-sum-1.0.2.tgz#33b40777754c6432573c120cc3808bbd10d47f04" integrity sha512-fUs4B4L+mlt8/XAtSOGMUO1TXmAelItBPtJG7CyHJfYTdDjwisntGO2JQz7oUsatOY9o68+57eziUVNw/mRHmA== -hash.js@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.3.tgz#340dedbe6290187151c1ea1d777a3448935df846" - integrity sha512-/UETyP0W22QILqS+6HowevwhEFJ3MBJnwTf75Qob9Wz9t0DPuisL8kW8YZMK62dHAKE1c1p+gY1TtOLY+USEHA== - dependencies: - inherits "^2.0.3" - minimalistic-assert "^1.0.0" - hash.js@1.1.7, hash.js@^1.0.0, hash.js@^1.0.3, hash.js@^1.1.7: version "1.1.7" resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" @@ -4073,7 +3855,14 @@ hash.js@1.1.7, hash.js@^1.0.0, hash.js@^1.0.3, hash.js@^1.1.7: inherits "^2.0.3" minimalistic-assert "^1.0.1" -he@1.2.0, he@^1.2.0: +hasown@^2.0.0, hasown@^2.0.1, hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + +he@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== @@ -4106,9 +3895,9 @@ html-minifier-terser@^6.0.2: terser "^5.10.0" html-webpack-plugin@^5.5.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-5.5.0.tgz#c3911936f57681c1f9f4d8b68c158cd9dfe52f50" - integrity sha512-sy88PC2cRTVxvETRgUHFrL4No3UxvcH8G1NepGhqaTT+GXN2kTamqasot0inS5hXeg1cMbFDt27zzo9p35lZVw== + version "5.6.0" + resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-5.6.0.tgz#50a8fa6709245608cb00e811eacecb8e0d7b7ea0" + integrity sha512-iwaY4wzbe48AfKLZ/Cc8k0L+FKG6oSNRaZ8x5A/T/IVDGyXcbHncM9TdDa93wn0FsSm82FhTKW7f3vS61thXAw== dependencies: "@types/html-minifier-terser" "^6.0.0" html-minifier-terser "^6.0.2" @@ -4154,15 +3943,6 @@ http-response-object@^3.0.1: dependencies: "@types/node" "^10.0.3" -http-signature@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" - integrity sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ== - dependencies: - assert-plus "^1.0.0" - jsprim "^1.2.2" - sshpk "^1.7.0" - https-proxy-agent@^5.0.0: version "5.0.1" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" @@ -4188,25 +3968,20 @@ icss-utils@^5.0.0, icss-utils@^5.1.0: resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae" integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA== -ieee754@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" - integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== - ignore@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== ignore@^5.1.1, ignore@^5.2.0: - version "5.2.4" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" - integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ== + version "5.3.1" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef" + integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw== immutable@^4.0.0-rc.12: - version "4.3.0" - resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.0.tgz#eb1738f14ffb39fd068b1dbe1296117484dd34be" - integrity sha512-0AOCmOip+xgJwEVTQj1EfiDDOkPmuyllDuTuEX+DDXUgapLAsBIfkg3sxCYyCEA8mQqZrrxPUGjcOQ2JS3WLkg== + version "4.3.7" + resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.7.tgz#c70145fc90d89fb02021e65c84eb0226e4e5a381" + integrity sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw== import-fresh@^2.0.0: version "2.0.0" @@ -4276,13 +4051,13 @@ inquirer@^6.2.2: strip-ansi "^5.1.0" through "^2.3.6" -internal-slot@^1.0.4, internal-slot@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.5.tgz#f2a2ee21f668f8627a4667f309dc0f4fb6674986" - integrity sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ== +internal-slot@^1.0.4, internal-slot@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.7.tgz#c06dcca3ed874249881007b0a5523b172a190802" + integrity sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g== dependencies: - get-intrinsic "^1.2.0" - has "^1.0.3" + es-errors "^1.3.0" + hasown "^2.0.0" side-channel "^1.0.4" interpret@^1.0.0: @@ -4305,20 +4080,26 @@ is-arguments@^1.1.1: call-bind "^1.0.2" has-tostringtag "^1.0.0" -is-array-buffer@^3.0.1, is-array-buffer@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.2.tgz#f2653ced8412081638ecb0ebbd0c41c6e0aecbbe" - integrity sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w== +is-array-buffer@^3.0.2, is-array-buffer@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.4.tgz#7a1f92b3d61edd2bc65d24f130530ea93d7fae98" + integrity sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw== dependencies: call-bind "^1.0.2" - get-intrinsic "^1.2.0" - is-typed-array "^1.1.10" + get-intrinsic "^1.2.1" is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== +is-async-function@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-async-function/-/is-async-function-2.0.0.tgz#8e4418efd3e5d3a6ebb0164c05ef5afb69aa9646" + integrity sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA== + dependencies: + has-tostringtag "^1.0.0" + is-bigint@^1.0.1: version "1.0.4" resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" @@ -4341,22 +4122,24 @@ is-boolean-object@^1.1.0: call-bind "^1.0.2" has-tostringtag "^1.0.0" -is-buffer@^2.0.5, is-buffer@~2.0.3: - version "2.0.5" - resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.5.tgz#ebc252e400d22ff8d77fa09888821a24a658c191" - integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ== - is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== -is-core-module@^2.11.0, is-core-module@^2.9.0: - version "2.11.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.11.0.tgz#ad4cb3e3863e814523c96f3f58d26cc570ff0144" - integrity sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw== +is-core-module@^2.13.0, is-core-module@^2.13.1: + version "2.15.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.15.0.tgz#71c72ec5442ace7e76b306e9d48db361f22699ea" + integrity sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA== + dependencies: + hasown "^2.0.2" + +is-data-view@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-data-view/-/is-data-view-1.0.1.tgz#4b4d3a511b70f3dc26d42c03ca9ca515d847759f" + integrity sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w== dependencies: - has "^1.0.3" + is-typed-array "^1.1.13" is-date-object@^1.0.1, is-date-object@^1.0.5: version "1.0.5" @@ -4375,6 +4158,13 @@ is-extglob@^2.1.1: resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== +is-finalizationregistry@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz#c8749b65f17c133313e661b1289b95ad3dbd62e6" + integrity sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw== + dependencies: + call-bind "^1.0.2" + is-fullwidth-code-point@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" @@ -4385,6 +4175,13 @@ is-fullwidth-code-point@^3.0.0: resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== +is-generator-function@^1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.10.tgz#f1558baf1ac17e0deea7c0415c438351ff2b3c72" + integrity sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A== + dependencies: + has-tostringtag "^1.0.0" + is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: version "4.0.3" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" @@ -4397,15 +4194,15 @@ is-hex-prefixed@1.0.0: resolved "https://registry.yarnpkg.com/is-hex-prefixed/-/is-hex-prefixed-1.0.0.tgz#7d8d37e6ad77e5d127148913c573e082d777f554" integrity sha512-WvtOiug1VFrE9v1Cydwm+FnXd3+w9GaeVUss5W4v/SLy3UW00vP+6iNF2SdnfiBoLy4bTqVdkftNGTUeOFVsbA== -is-map@^2.0.1, is-map@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.2.tgz#00922db8c9bf73e81b7a335827bc2a43f2b91127" - integrity sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg== +is-map@^2.0.2, is-map@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.3.tgz#ede96b7fe1e270b3c4465e3a465658764926d62e" + integrity sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw== -is-negative-zero@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.2.tgz#7bf6f03a28003b8b3965de3ac26f664d765f3150" - integrity sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA== +is-negative-zero@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.3.tgz#ced903a027aca6381b777a5743069d7376a49747" + integrity sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw== is-number-object@^1.0.4: version "1.0.7" @@ -4437,17 +4234,17 @@ is-regex@^1.1.4: call-bind "^1.0.2" has-tostringtag "^1.0.0" -is-set@^2.0.1, is-set@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.2.tgz#90755fa4c2562dc1c5d4024760d6119b94ca18ec" - integrity sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g== +is-set@^2.0.2, is-set@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.3.tgz#8ab209ea424608141372ded6e0cb200ef1d9d01d" + integrity sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg== -is-shared-array-buffer@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz#8f259c573b60b6a32d4058a1a07430c0a7344c79" - integrity sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA== +is-shared-array-buffer@^1.0.2, is-shared-array-buffer@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz#1237f1cba059cdb62431d378dcc37d9680181688" + integrity sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg== dependencies: - call-bind "^1.0.2" + call-bind "^1.0.7" is-string@^1.0.5, is-string@^1.0.7: version "1.0.7" @@ -4463,31 +4260,22 @@ is-symbol@^1.0.2, is-symbol@^1.0.3: dependencies: has-symbols "^1.0.2" -is-typed-array@^1.1.10, is-typed-array@^1.1.9: - version "1.1.10" - resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.10.tgz#36a5b5cb4189b575d1a3e4b08536bfb485801e3f" - integrity sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A== +is-typed-array@^1.1.13: + version "1.1.13" + resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.13.tgz#d6c5ca56df62334959322d7d7dd1cca50debe229" + integrity sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw== dependencies: - available-typed-arrays "^1.0.5" - call-bind "^1.0.2" - for-each "^0.3.3" - gopd "^1.0.1" - has-tostringtag "^1.0.0" - -is-typedarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" - integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== + which-typed-array "^1.1.14" is-unicode-supported@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== -is-weakmap@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.1.tgz#5008b59bdc43b698201d18f62b37b2ca243e8cf2" - integrity sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA== +is-weakmap@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.2.tgz#bf72615d649dfe5f699079c54b83e47d1ae19cfd" + integrity sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w== is-weakref@^1.0.2: version "1.0.2" @@ -4496,13 +4284,13 @@ is-weakref@^1.0.2: dependencies: call-bind "^1.0.2" -is-weakset@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.2.tgz#4569d67a747a1ce5a994dfd4ef6dcea76e7c0a1d" - integrity sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg== +is-weakset@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.3.tgz#e801519df8c0c43e12ff2834eead84ec9e624007" + integrity sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ== dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.1.1" + call-bind "^1.0.7" + get-intrinsic "^1.2.4" isarray@^2.0.5: version "2.0.5" @@ -4519,10 +4307,16 @@ isexe@^2.0.0: resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== -isstream@~0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" - integrity sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g== +iterator.prototype@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/iterator.prototype/-/iterator.prototype-1.1.2.tgz#5e29c8924f01916cb9335f1ff80619dcff22b0c0" + integrity sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w== + dependencies: + define-properties "^1.2.1" + get-intrinsic "^1.2.1" + has-symbols "^1.0.3" + reflect.getprototypeof "^1.0.4" + set-function-name "^2.0.1" javascript-natural-sort@^0.7.1: version "0.7.1" @@ -4538,16 +4332,6 @@ jest-worker@^27.4.5: merge-stream "^2.0.0" supports-color "^8.0.0" -js-sdsl@^4.1.4: - version "4.4.0" - resolved "https://registry.yarnpkg.com/js-sdsl/-/js-sdsl-4.4.0.tgz#8b437dbe642daa95760400b602378ed8ffea8430" - integrity sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg== - -js-sha3@0.5.7: - version "0.5.7" - resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.5.7.tgz#0d4ffd8002d5333aabaf4a23eed2f6374c9f28e7" - integrity sha512-GII20kjaPX0zJ8wzkTbNDYMY7msuZcTWk8S5UOh6806Jq/wz1J8/bnr8uGU0DAUmYDjj2Mr4X1cW8v/GLYnR+g== - js-sha3@0.8.0, js-sha3@^0.8.0: version "0.8.0" resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840" @@ -4558,14 +4342,6 @@ js-tokens@^4.0.0: resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== -js-yaml@3.13.1: - version "3.13.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" - integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - js-yaml@3.x, js-yaml@^3.12.0, js-yaml@^3.13.0, js-yaml@^3.13.1: version "3.14.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" @@ -4574,17 +4350,17 @@ js-yaml@3.x, js-yaml@^3.12.0, js-yaml@^3.13.0, js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" -js-yaml@4.1.0, js-yaml@^4.1.0: +js-yaml@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== dependencies: argparse "^2.0.1" -jsbn@~0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" - integrity sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg== +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== json-parse-better-errors@^1.0.1: version "1.0.2" @@ -4601,7 +4377,7 @@ json-schema-traverse@^0.4.1: resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== -json-schema@0.4.0, json-schema@^0.4.0: +json-schema@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.4.0.tgz#f7de4cf6efab838ebaeb3236474cbba5a1930ab5" integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA== @@ -4611,11 +4387,6 @@ json-stable-stringify-without-jsonify@^1.0.1: resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== -json-stringify-safe@~5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" - integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== - json5@^1.0.1, json5@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593" @@ -4623,13 +4394,6 @@ json5@^1.0.1, json5@^1.0.2: dependencies: minimist "^1.2.0" -jsonfile@^2.1.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-2.4.0.tgz#3736a2b428b87bbda0cc83b53fa3d633a35c2ae8" - integrity sha512-PKllAqbgLgxHaj8TElYymKCAgrASebJrWpTnEkOaTowt23VKXXN0sUeriJ+eh7y6ufb/CC5ap11pz71/cM0hUw== - optionalDependencies: - graceful-fs "^4.1.6" - jsonfile@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" @@ -4651,77 +4415,48 @@ jsonschema@^1.2.4: resolved "https://registry.yarnpkg.com/jsonschema/-/jsonschema-1.4.1.tgz#cc4c3f0077fb4542982973d8a083b6b34f482dab" integrity sha512-S6cATIPVv1z0IlxdN+zUk5EPjkGCdnhN4wVSBlvoUO1tOLJootbo9CquNJmbIh4yikWHiUedhRYrNPn1arpEmQ== -jsprim@^1.2.2: - version "1.4.2" - resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.2.tgz#712c65533a15c878ba59e9ed5f0e26d5b77c5feb" - integrity sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw== - dependencies: - assert-plus "1.0.0" - extsprintf "1.3.0" - json-schema "0.4.0" - verror "1.10.0" - -jsx-ast-utils@^3.3.3: - version "3.3.3" - resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz#76b3e6e6cece5c69d49a5792c3d01bd1a0cdc7ea" - integrity sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw== +jsx-ast-utils@^3.3.5: + version "3.3.5" + resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz#4766bd05a8e2a11af222becd19e15575e52a853a" + integrity sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ== dependencies: - array-includes "^3.1.5" - object.assign "^4.1.3" + array-includes "^3.1.6" + array.prototype.flat "^1.3.1" + object.assign "^4.1.4" + object.values "^1.1.6" keccak@^3.0.0, keccak@^3.0.2: - version "3.0.3" - resolved "https://registry.yarnpkg.com/keccak/-/keccak-3.0.3.tgz#4bc35ad917be1ef54ff246f904c2bbbf9ac61276" - integrity sha512-JZrLIAJWuZxKbCilMpNz5Vj7Vtb4scDG3dMXLOsbzBmQGyjwE61BbW7bJkfKKCShXiQZt3T6sBgALRtmd+nZaQ== + version "3.0.4" + resolved "https://registry.yarnpkg.com/keccak/-/keccak-3.0.4.tgz#edc09b89e633c0549da444432ecf062ffadee86d" + integrity sha512-3vKuW0jV8J3XNTzvfyicFR5qvxrSAGl7KIhvgOu5cmWwM7tZRj3fMbj/pfIf4be7aznbc+prBWGjywox/g2Y6Q== dependencies: node-addon-api "^2.0.0" node-gyp-build "^4.2.0" readable-stream "^3.6.0" +keyv@^4.5.3: + version "4.5.4" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" + integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== + dependencies: + json-buffer "3.0.1" + kind-of@^6.0.2: version "6.0.3" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== -klaw@^1.0.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/klaw/-/klaw-1.3.1.tgz#4088433b46b3b1ba259d78785d8e96f73ba02439" - integrity sha512-TED5xi9gGQjGpNnvRWknrwAB1eL5GciPfVFOt3Vk1OJCVDQbzuSfrF3hkUQKlsgKrG1F+0t5W0m+Fje1jIt8rw== - optionalDependencies: - graceful-fs "^4.1.9" - -language-subtag-registry@~0.3.2: - version "0.3.22" - resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz#2e1500861b2e457eba7e7ae86877cbd08fa1fd1d" - integrity sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w== +language-subtag-registry@^0.3.20: + version "0.3.23" + resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz#23529e04d9e3b74679d70142df3fd2eb6ec572e7" + integrity sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ== -language-tags@=1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/language-tags/-/language-tags-1.0.5.tgz#d321dbc4da30ba8bf3024e040fa5c14661f9193a" - integrity sha512-qJhlO9cGXi6hBGKoxEG/sKZDAHD5Hnu9Hs4WbOY3pCWXDhw0N8x1NenNzm2EnNLkLkk7J2SdxAkDSbb6ftT+UQ== - dependencies: - language-subtag-registry "~0.3.2" - -level-supports@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/level-supports/-/level-supports-4.0.1.tgz#431546f9d81f10ff0fea0e74533a0e875c08c66a" - integrity sha512-PbXpve8rKeNcZ9C1mUicC9auIYFyGpkV9/i6g76tLgANwWhtG2v7I4xNBUlkn3lE2/dZF3Pi0ygYGtLc4RXXdA== - -level-transcoder@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/level-transcoder/-/level-transcoder-1.0.1.tgz#f8cef5990c4f1283d4c86d949e73631b0bc8ba9c" - integrity sha512-t7bFwFtsQeD8cl8NIoQ2iwxA0CL/9IFw7/9gAjOonH0PWTTiRfY7Hq+Ejbsxh86tXobDQ6IOiddjNYIfOBs06w== - dependencies: - buffer "^6.0.3" - module-error "^1.0.1" - -level@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/level/-/level-8.0.0.tgz#41b4c515dabe28212a3e881b61c161ffead14394" - integrity sha512-ypf0jjAk2BWI33yzEaaotpq7fkOPALKAgDBxggO6Q9HGX2MRXn0wbP1Jn/tJv1gtL867+YOjOB49WaUF3UoJNQ== +language-tags@^1.0.9: + version "1.0.9" + resolved "https://registry.yarnpkg.com/language-tags/-/language-tags-1.0.9.tgz#1ffdcd0ec0fafb4b1be7f8b11f306ad0f9c08777" + integrity sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA== dependencies: - browser-level "^1.0.1" - classic-level "^1.2.0" + language-subtag-registry "^0.3.20" levn@^0.3.0, levn@~0.3.0: version "0.3.0" @@ -4761,14 +4496,6 @@ locate-path@^2.0.0: p-locate "^2.0.0" path-exists "^3.0.0" -locate-path@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" - integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== - dependencies: - p-locate "^3.0.0" - path-exists "^3.0.0" - locate-path@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" @@ -4796,19 +4523,12 @@ lodash.merge@^4.6.2: resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== -lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.16, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21: +lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.21: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== -log-symbols@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-3.0.0.tgz#f3a08516a5dea893336a7dee14d18a1cfdab77c4" - integrity sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ== - dependencies: - chalk "^2.4.2" - -log-symbols@4.1.0: +log-symbols@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== @@ -4816,12 +4536,12 @@ log-symbols@4.1.0: chalk "^4.1.0" is-unicode-supported "^0.1.0" -loupe@^2.3.1: - version "2.3.6" - resolved "https://registry.yarnpkg.com/loupe/-/loupe-2.3.6.tgz#76e4af498103c532d1ecc9be102036a21f787b53" - integrity sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA== +loupe@^2.3.6: + version "2.3.7" + resolved "https://registry.yarnpkg.com/loupe/-/loupe-2.3.7.tgz#6e69b7d4db7d3ab436328013d37d1c8c3540c697" + integrity sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA== dependencies: - get-func-name "^2.0.0" + get-func-name "^2.0.1" lower-case@^2.0.2: version "2.0.2" @@ -4838,20 +4558,6 @@ lru-cache@^4.1.2, lru-cache@^4.1.5: pseudomap "^1.0.2" yallist "^2.1.2" -lru-cache@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" - integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== - dependencies: - yallist "^3.0.2" - -lru-cache@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" - integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== - dependencies: - yallist "^4.0.0" - lru_map@^0.3.3: version "0.3.3" resolved "https://registry.yarnpkg.com/lru_map/-/lru_map-0.3.3.tgz#b5c8351b9464cbd750335a79650a0ec0e56118dd" @@ -4887,11 +4593,6 @@ mathjs@^10.4.0: tiny-emitter "^2.1.0" typed-function "^2.1.0" -mcl-wasm@^0.7.1: - version "0.7.9" - resolved "https://registry.yarnpkg.com/mcl-wasm/-/mcl-wasm-0.7.9.tgz#c1588ce90042a8700c3b60e40efb339fc07ab87f" - integrity sha512-iJIUcQWA88IJB/5L15GnJVnSQJmf/YaxxV6zRavv83HILHaJQb6y0iFyDMdDO0gN8X37tdxmAOrH/P8B6RB8sQ== - md5.js@^1.3.4: version "1.3.5" resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" @@ -4901,15 +4602,6 @@ md5.js@^1.3.4: inherits "^2.0.1" safe-buffer "^5.1.2" -memory-level@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/memory-level/-/memory-level-1.0.0.tgz#7323c3fd368f9af2f71c3cd76ba403a17ac41692" - integrity sha512-UXzwewuWeHBz5krr7EvehKcmLFNoXxGcvuYhC41tRnkrTbJohtS7kVn9akmgirtRygg+f7Yjsfi8Uu5SGSQ4Og== - dependencies: - abstract-level "^1.0.0" - functional-red-black-tree "^1.0.1" - module-error "^1.0.1" - memorystream@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/memorystream/-/memorystream-0.3.1.tgz#86d7090b30ce455d63fbae12dda51a47ddcaf9b2" @@ -4932,12 +4624,17 @@ merge2@^1.2.3, merge2@^1.3.0, merge2@^1.4.1: resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== +micro-ftch@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/micro-ftch/-/micro-ftch-0.3.1.tgz#6cb83388de4c1f279a034fb0cf96dfc050853c5f" + integrity sha512-/0LLxhzP0tfiR5hcQebtudP56gUurs2CLkGarnCiB/OqEyUFQ6U3paQi/tgLv0hBJYt2rnr9MNpxz4fiiugstg== + micromatch@^4.0.4: - version "4.0.5" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" - integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== + version "4.0.7" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.7.tgz#33e8190d9fe474a9895525f5618eee136d46c2e5" + integrity sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q== dependencies: - braces "^3.0.2" + braces "^3.0.3" picomatch "^2.3.1" mime-db@1.52.0: @@ -4945,7 +4642,7 @@ mime-db@1.52.0: resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== -mime-types@^2.1.12, mime-types@^2.1.27, mime-types@~2.1.19: +mime-types@^2.1.12, mime-types@^2.1.27: version "2.1.35" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== @@ -4974,17 +4671,10 @@ minimalistic-crypto-utils@^1.0.1: dependencies: brace-expansion "^1.1.7" -minimatch@3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" - integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== - dependencies: - brace-expansion "^1.1.7" - -minimatch@5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.0.1.tgz#fb9022f7528125187c92bd9e9b6366be1cf3415b" - integrity sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g== +minimatch@^5.0.1, minimatch@^5.1.6: + version "5.1.6" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" + integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== dependencies: brace-expansion "^2.0.1" @@ -4993,13 +4683,6 @@ minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== -mkdirp@0.5.5: - version "0.5.5" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" - integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== - dependencies: - minimist "^1.2.5" - mkdirp@0.5.x, mkdirp@^0.5.1: version "0.5.6" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" @@ -5019,109 +4702,38 @@ mnemonist@^0.38.0: dependencies: obliterator "^2.0.0" -mocha@7.1.2: - version "7.1.2" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-7.1.2.tgz#8e40d198acf91a52ace122cd7599c9ab857b29e6" - integrity sha512-o96kdRKMKI3E8U0bjnfqW4QMk12MwZ4mhdBTf+B5a1q9+aq2HRnj+3ZdJu0B/ZhJeK78MgYuv6L8d/rA5AeBJA== - dependencies: - ansi-colors "3.2.3" - browser-stdout "1.3.1" - chokidar "3.3.0" - debug "3.2.6" - diff "3.5.0" - escape-string-regexp "1.0.5" - find-up "3.0.0" - glob "7.1.3" - growl "1.10.5" - he "1.2.0" - js-yaml "3.13.1" - log-symbols "3.0.0" - minimatch "3.0.4" - mkdirp "0.5.5" - ms "2.1.1" - node-environment-flags "1.0.6" - object.assign "4.1.0" - strip-json-comments "2.0.1" - supports-color "6.0.0" - which "1.3.1" - wide-align "1.1.3" - yargs "13.3.2" - yargs-parser "13.1.2" - yargs-unparser "1.6.0" - -mocha@^10.0.0: - version "10.2.0" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.2.0.tgz#1fd4a7c32ba5ac372e03a17eef435bd00e5c68b8" - integrity sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg== - dependencies: - ansi-colors "4.1.1" - browser-stdout "1.3.1" - chokidar "3.5.3" - debug "4.3.4" - diff "5.0.0" - escape-string-regexp "4.0.0" - find-up "5.0.0" - glob "7.2.0" - he "1.2.0" - js-yaml "4.1.0" - log-symbols "4.1.0" - minimatch "5.0.1" - ms "2.1.3" - nanoid "3.3.3" - serialize-javascript "6.0.0" - strip-json-comments "3.1.1" - supports-color "8.1.1" - workerpool "6.2.1" - yargs "16.2.0" - yargs-parser "20.2.4" - yargs-unparser "2.0.0" - -mocha@^7.1.1: - version "7.2.0" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-7.2.0.tgz#01cc227b00d875ab1eed03a75106689cfed5a604" - integrity sha512-O9CIypScywTVpNaRrCAgoUnJgozpIofjKUYmJhiCIJMiuYnLI6otcb1/kpW9/n/tJODHGZ7i8aLQoDVsMtOKQQ== - dependencies: - ansi-colors "3.2.3" - browser-stdout "1.3.1" - chokidar "3.3.0" - debug "3.2.6" - diff "3.5.0" - escape-string-regexp "1.0.5" - find-up "3.0.0" - glob "7.1.3" - growl "1.10.5" - he "1.2.0" - js-yaml "3.13.1" - log-symbols "3.0.0" - minimatch "3.0.4" - mkdirp "0.5.5" - ms "2.1.1" - node-environment-flags "1.0.6" - object.assign "4.1.0" - strip-json-comments "2.0.1" - supports-color "6.0.0" - which "1.3.1" - wide-align "1.1.3" - yargs "13.3.2" - yargs-parser "13.1.2" - yargs-unparser "1.6.0" - -module-error@^1.0.1, module-error@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/module-error/-/module-error-1.0.2.tgz#8d1a48897ca883f47a45816d4fb3e3c6ba404d86" - integrity sha512-0yuvsqSCv8LbaOKhnsQ/T5JhyFlCYLPXK3U2sgV10zoKQwzs/MyfuQUOZQ1V/6OCOJsK/TRgNVrPuPDqtdMFtA== - -ms@2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" - integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== +mocha@^10.0.0, mocha@^10.2.0: + version "10.7.0" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.7.0.tgz#9e5cbed8fa9b37537a25bd1f7fb4f6fc45458b9a" + integrity sha512-v8/rBWr2VO5YkspYINnvu81inSz2y3ODJrhO175/Exzor1RcEZZkizgE2A+w/CAXXoESS8Kys5E62dOHGHzULA== + dependencies: + ansi-colors "^4.1.3" + browser-stdout "^1.3.1" + chokidar "^3.5.3" + debug "^4.3.5" + diff "^5.2.0" + escape-string-regexp "^4.0.0" + find-up "^5.0.0" + glob "^8.1.0" + he "^1.2.0" + js-yaml "^4.1.0" + log-symbols "^4.1.0" + minimatch "^5.1.6" + ms "^2.1.3" + serialize-javascript "^6.0.2" + strip-json-comments "^3.1.1" + supports-color "^8.1.1" + workerpool "^6.5.1" + yargs "^16.2.0" + yargs-parser "^20.2.9" + yargs-unparser "^2.0.0" ms@2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@2.1.3, ms@^2.1.1: +ms@^2.1.1, ms@^2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== @@ -5140,20 +4752,10 @@ mute-stream@0.0.7: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" integrity sha512-r65nCZhrbXXb6dXOACihYApHw2Q6pV0M3V0PSxd74N0+D8nzAdEAITq2oAjA1jVnKI+tGvEBUpqiMh0+rW6zDQ== -nanoid@3.3.3: - version "3.3.3" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.3.tgz#fd8e8b7aa761fe807dba2d1b98fb7241bb724a25" - integrity sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w== - -nanoid@^3.3.4: - version "3.3.6" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c" - integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA== - -napi-macros@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/napi-macros/-/napi-macros-2.0.0.tgz#2b6bae421e7b96eb687aa6c77a7858640670001b" - integrity sha512-A0xLykHtARfueITVDernsAWdtIMbOJgKgcluwENp3AlsKN/PloyO10HtmoqnFAQAcxPkgZN7wdfPfEd0zNGxbg== +nanoid@^3.3.7: + version "3.3.7" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" + integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== natural-compare-lite@^1.4.0: version "1.4.0" @@ -5165,7 +4767,7 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== -neo-async@^2.6.0, neo-async@^2.6.2: +neo-async@^2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== @@ -5195,23 +4797,15 @@ node-emoji@^1.10.0: dependencies: lodash "^4.17.21" -node-environment-flags@1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/node-environment-flags/-/node-environment-flags-1.0.6.tgz#a30ac13621f6f7d674260a54dede048c3982c088" - integrity sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw== - dependencies: - object.getownpropertydescriptors "^2.0.3" - semver "^5.7.0" +node-gyp-build@^4.2.0: + version "4.8.1" + resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.8.1.tgz#976d3ad905e71b76086f4f0b0d3637fe79b6cda5" + integrity sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw== -node-gyp-build@^4.2.0, node-gyp-build@^4.3.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.6.0.tgz#0c52e4cbf54bbd28b709820ef7b6a3c2d6209055" - integrity sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ== - -node-releases@^2.0.8: - version "2.0.10" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.10.tgz#c311ebae3b6a148c89b1813fd7c4d3c024ef537f" - integrity sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w== +node-releases@^2.0.14: + version "2.0.18" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.18.tgz#f010e8d35e2fe8d6b2944f03f70213ecedc4ca3f" + integrity sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g== nopt@3.x: version "3.0.6" @@ -5240,90 +4834,75 @@ number-to-bn@1.7.0: bn.js "4.11.6" strip-hex-prefix "1.0.0" -oauth-sign@~0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" - integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== - object-assign@^4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== -object-inspect@^1.12.3, object-inspect@^1.9.0: - version "1.12.3" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.3.tgz#ba62dffd67ee256c8c086dfae69e016cd1f198b9" - integrity sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g== +object-inspect@^1.13.1: + version "1.13.2" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.2.tgz#dea0088467fb991e67af4058147a24824a3043ff" + integrity sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g== object-is@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.5.tgz#b9deeaa5fc7f1846a0faecdceec138e5778f53ac" - integrity sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw== + version "1.1.6" + resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.6.tgz#1a6a53aed2dd8f7e6775ff870bea58545956ab07" + integrity sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q== dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" + call-bind "^1.0.7" + define-properties "^1.2.1" -object-keys@^1.0.11, object-keys@^1.1.1: +object-keys@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== -object.assign@4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" - integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w== - dependencies: - define-properties "^1.1.2" - function-bind "^1.1.1" - has-symbols "^1.0.0" - object-keys "^1.0.11" - -object.assign@^4.1.2, object.assign@^4.1.3, object.assign@^4.1.4: - version "4.1.4" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.4.tgz#9673c7c7c351ab8c4d0b516f4343ebf4dfb7799f" - integrity sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ== +object.assign@^4.1.2, object.assign@^4.1.4, object.assign@^4.1.5: + version "4.1.5" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.5.tgz#3a833f9ab7fdb80fc9e8d2300c803d216d8fdbb0" + integrity sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ== dependencies: - call-bind "^1.0.2" - define-properties "^1.1.4" + call-bind "^1.0.5" + define-properties "^1.2.1" has-symbols "^1.0.3" object-keys "^1.1.1" -object.entries@^1.1.5, object.entries@^1.1.6: - version "1.1.6" - resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.6.tgz#9737d0e5b8291edd340a3e3264bb8a3b00d5fa23" - integrity sha512-leTPzo4Zvg3pmbQ3rDK69Rl8GQvIqMWubrkxONG9/ojtFE2rD9fjMKfSI5BxW3osRH1m6VdzmqK8oAY9aT4x5w== +object.entries@^1.1.5: + version "1.1.8" + resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.8.tgz#bffe6f282e01f4d17807204a24f8edd823599c41" + integrity sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ== dependencies: - call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" + call-bind "^1.0.7" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" -object.fromentries@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.6.tgz#cdb04da08c539cffa912dcd368b886e0904bfa73" - integrity sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg== +object.fromentries@^2.0.7, object.fromentries@^2.0.8: + version "2.0.8" + resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.8.tgz#f7195d8a9b97bd95cbc1999ea939ecd1a2b00c65" + integrity sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ== dependencies: - call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-object-atoms "^1.0.0" -object.getownpropertydescriptors@^2.0.3: - version "2.1.5" - resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.5.tgz#db5a9002489b64eef903df81d6623c07e5b4b4d3" - integrity sha512-yDNzckpM6ntyQiGTik1fKV1DcVDRS+w8bvpWNCBanvH5LfRX9O8WTHqQzG4RZwRAM4I0oU7TV11Lj5v0g20ibw== +object.groupby@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/object.groupby/-/object.groupby-1.0.3.tgz#9b125c36238129f6f7b61954a1e7176148d5002e" + integrity sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ== dependencies: - array.prototype.reduce "^1.0.5" - call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" -object.values@^1.1.6: - version "1.1.6" - resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.6.tgz#4abbaa71eba47d63589d402856f908243eea9b1d" - integrity sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw== +object.values@^1.1.6, object.values@^1.1.7: + version "1.2.0" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.2.0.tgz#65405a9d92cee68ac2d303002e0b8470a4d9ab1b" + integrity sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ== dependencies: - call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" + call-bind "^1.0.7" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" obliterator@^2.0.0: version "2.0.4" @@ -5356,17 +4935,17 @@ optionator@^0.8.1, optionator@^0.8.2: type-check "~0.3.2" word-wrap "~1.2.3" -optionator@^0.9.1: - version "0.9.1" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" - integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== +optionator@^0.9.3: + version "0.9.4" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" + integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== dependencies: deep-is "^0.1.3" fast-levenshtein "^2.0.6" levn "^0.4.1" prelude-ls "^1.2.1" type-check "^0.4.0" - word-wrap "^1.2.3" + word-wrap "^1.2.5" ordinal@^1.0.3: version "1.0.3" @@ -5385,13 +4964,6 @@ p-limit@^1.1.0: dependencies: p-try "^1.0.0" -p-limit@^2.0.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" - integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== - dependencies: - p-try "^2.0.0" - p-limit@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" @@ -5406,13 +4978,6 @@ p-locate@^2.0.0: dependencies: p-limit "^1.1.0" -p-locate@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" - integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== - dependencies: - p-limit "^2.0.0" - p-locate@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" @@ -5432,11 +4997,6 @@ p-try@^1.0.0: resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" integrity sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww== -p-try@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" - integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== - param-case@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/param-case/-/param-case-3.0.4.tgz#7d17fe4aa12bde34d4a77d91acfb6219caad01c5" @@ -5529,20 +5089,15 @@ pbkdf2@^3.0.17: safe-buffer "^5.0.1" sha.js "^2.4.8" -performance-now@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" - integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow== - picocolors@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-0.2.1.tgz#570670f793646851d1ba135996962abad587859f" integrity sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA== -picocolors@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" - integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== +picocolors@^1.0.0, picocolors@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.1.tgz#a8ad579b571952f0e5d25892de5445bcfe25aaa1" + integrity sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew== picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: version "2.3.1" @@ -5554,24 +5109,29 @@ pify@^4.0.1: resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== -postcss-modules-extract-imports@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz#cda1f047c0ae80c97dbe28c3e76a43b88025741d" - integrity sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw== +possible-typed-array-names@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz#89bb63c6fada2c3e90adc4a647beeeb39cc7bf8f" + integrity sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q== -postcss-modules-local-by-default@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz#ebbb54fae1598eecfdf691a02b3ff3b390a5a51c" - integrity sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ== +postcss-modules-extract-imports@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz#b4497cb85a9c0c4b5aabeb759bb25e8d89f15002" + integrity sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q== + +postcss-modules-local-by-default@^4.0.5: + version "4.0.5" + resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.5.tgz#f1b9bd757a8edf4d8556e8d0f4f894260e3df78f" + integrity sha512-6MieY7sIfTK0hYfafw1OMEG+2bg8Q1ocHCpoWLqOKj3JXlKu4G7btkmM/B7lFubYkYWmRSPLZi5chid63ZaZYw== dependencies: icss-utils "^5.0.0" postcss-selector-parser "^6.0.2" postcss-value-parser "^4.1.0" -postcss-modules-scope@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz#9ef3151456d3bbfa120ca44898dfca6f2fa01f06" - integrity sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg== +postcss-modules-scope@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-3.2.0.tgz#a43d28289a169ce2c15c00c4e64c0858e43457d5" + integrity sha512-oq+g1ssrsZOsx9M96c5w8laRmvEu9C3adDSjI8oTcbfkrTE8hx/zfyobUoWIxaKPO8bt6S62kxpw5GqypEw1QQ== dependencies: postcss-selector-parser "^6.0.4" @@ -5583,9 +5143,9 @@ postcss-modules-values@^4.0.0: icss-utils "^5.0.0" postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4: - version "6.0.11" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.11.tgz#2e41dc39b7ad74046e1615185185cd0b17d0c8dc" - integrity sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g== + version "6.1.1" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.1.1.tgz#5be94b277b8955904476a2400260002ce6c56e38" + integrity sha512-b4dlw/9V8A71rLIDsSwVmak9z2DuBUB7CA1/wSdelNEzqsjoSPeADTWNO09lpH49Diy3/JIZ2bSPB1dI3LJCHg== dependencies: cssesc "^3.0.0" util-deprecate "^1.0.2" @@ -5603,14 +5163,14 @@ postcss@^7.0.36: picocolors "^0.2.1" source-map "^0.6.1" -postcss@^8.4.14, postcss@^8.4.19: - version "8.4.21" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.21.tgz#c639b719a57efc3187b13a1d765675485f4134f4" - integrity sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg== +postcss@^8.4.14, postcss@^8.4.33: + version "8.4.40" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.40.tgz#eb81f2a4dd7668ed869a6db25999e02e9ad909d8" + integrity sha512-YF2kKIUzAofPMpfH6hOi2cGnv/HrUlfucspc7pDyvv7kGdqXrfj8SCl/t8owkEgKEuu8ZcRjSOxFxVLqwChZ2Q== dependencies: - nanoid "^3.3.4" - picocolors "^1.0.0" - source-map-js "^1.0.2" + nanoid "^3.3.7" + picocolors "^1.0.1" + source-map-js "^1.2.0" prelude-ls@^1.2.1: version "1.2.1" @@ -5630,13 +5190,13 @@ prettier-linter-helpers@^1.0.0: fast-diff "^1.1.2" prettier-plugin-solidity@^1.0.0-beta.24: - version "1.1.3" - resolved "https://registry.yarnpkg.com/prettier-plugin-solidity/-/prettier-plugin-solidity-1.1.3.tgz#9a35124f578404caf617634a8cab80862d726cba" - integrity sha512-fQ9yucPi2sBbA2U2Xjh6m4isUTJ7S7QLc/XDDsktqqxYfTwdYKJ0EnnywXHwCGAaYbQNK+HIYPL1OemxuMsgeg== + version "1.3.1" + resolved "https://registry.yarnpkg.com/prettier-plugin-solidity/-/prettier-plugin-solidity-1.3.1.tgz#59944d3155b249f7f234dee29f433524b9a4abcf" + integrity sha512-MN4OP5I2gHAzHZG1wcuJl0FsLS3c4Cc5494bbg+6oQWBPuEamjwDvmGfFMZ6NFzsh3Efd9UUxeT7ImgjNH4ozA== dependencies: - "@solidity-parser/parser" "^0.16.0" - semver "^7.3.8" - solidity-comments-extractor "^0.0.7" + "@solidity-parser/parser" "^0.17.0" + semver "^7.5.4" + solidity-comments-extractor "^0.0.8" prettier@^1.14.3: version "1.19.1" @@ -5644,9 +5204,9 @@ prettier@^1.14.3: integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew== "prettier@^1.18.2 || ^2.0.0", prettier@^2.3.1, prettier@^2.7.1: - version "2.8.7" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.7.tgz#bb79fc8729308549d28fe3a98fce73d2c0656450" - integrity sha512-yPngTo3aXUUmyuTjeTUT75txrf+aMh9FiD7q9ZE/i6r0bPb22g4FsE6Y338PQX1bmfy08i9QQCB7/rcUAVntfw== + version "2.8.8" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" + integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== pretty-error@^4.0.0: version "4.0.0" @@ -5683,29 +5243,19 @@ pseudomap@^1.0.2: resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" integrity sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ== -psl@^1.1.28: - version "1.9.0" - resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7" - integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag== - -punycode@^2.1.0, punycode@^2.1.1: - version "2.3.0" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f" - integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA== +punycode@^2.1.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== -qs@^6.4.0, qs@^6.7.0, qs@^6.9.4: - version "6.11.1" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.1.tgz#6c29dff97f0c0060765911ba65cbc9764186109f" - integrity sha512-0wsrzgTz/kAVIeuxSjnpGC56rzYtr6JT/2BwEvMaPhFIoYa1aGO8LbzuU1R0uUYQkLpWBTOj0l/CLAJB64J6nQ== +qs@^6.4.0, qs@^6.9.4: + version "6.12.3" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.12.3.tgz#e43ce03c8521b9c7fd7f1f13e514e5ca37727754" + integrity sha512-AWJm14H1vVaO/iNZ4/hO+HyaTehuy9nRqVdkTqlJt0HWvBiBIEXFmb4C0DGeYo3Xes9rrEW+TxHsaigCbN5ICQ== dependencies: - side-channel "^1.0.4" + side-channel "^1.0.6" -qs@~6.5.2: - version "6.5.3" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.3.tgz#3aeeffc91967ef6e35c0e488ef46fb296ab76aad" - integrity sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA== - -queue-microtask@^1.2.2, queue-microtask@^1.2.3: +queue-microtask@^1.2.2: version "1.2.3" resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== @@ -5749,13 +5299,6 @@ readable-stream@^3.6.0: string_decoder "^1.1.1" util-deprecate "^1.0.1" -readdirp@~3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.2.0.tgz#c30c33352b12c96dfb4b895421a49fd5a9593839" - integrity sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ== - dependencies: - picomatch "^2.0.4" - readdirp@~3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" @@ -5782,19 +5325,33 @@ reduce-flatten@^2.0.0: resolved "https://registry.yarnpkg.com/reduce-flatten/-/reduce-flatten-2.0.0.tgz#734fd84e65f375d7ca4465c69798c25c9d10ae27" integrity sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w== -regenerator-runtime@^0.13.11: - version "0.13.11" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" - integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== +reflect.getprototypeof@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz#3ab04c32a8390b770712b7a8633972702d278859" + integrity sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.1" + es-errors "^1.3.0" + get-intrinsic "^1.2.4" + globalthis "^1.0.3" + which-builtin-type "^1.1.3" -regexp.prototype.flags@^1.4.3: - version "1.4.3" - resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz#87cab30f80f66660181a3bb7bf5981a872b367ac" - integrity sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA== +regenerator-runtime@^0.14.0: + version "0.14.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f" + integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw== + +regexp.prototype.flags@^1.5.1, regexp.prototype.flags@^1.5.2: + version "1.5.2" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz#138f644a3350f981a858c44f6bb1a61ff59be334" + integrity sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw== dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" - functions-have-names "^1.2.2" + call-bind "^1.0.6" + define-properties "^1.2.1" + es-errors "^1.3.0" + set-function-name "^2.0.1" regexpp@^2.0.1: version "2.0.1" @@ -5831,63 +5388,11 @@ req-from@^2.0.0: dependencies: resolve-from "^3.0.0" -request-promise-core@1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.4.tgz#3eedd4223208d419867b78ce815167d10593a22f" - integrity sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw== - dependencies: - lodash "^4.17.19" - -request-promise-native@^1.0.5: - version "1.0.9" - resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.9.tgz#e407120526a5efdc9a39b28a5679bf47b9d9dc28" - integrity sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g== - dependencies: - request-promise-core "1.1.4" - stealthy-require "^1.1.1" - tough-cookie "^2.3.3" - -request@^2.88.0: - version "2.88.2" - resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" - integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== - dependencies: - aws-sign2 "~0.7.0" - aws4 "^1.8.0" - caseless "~0.12.0" - combined-stream "~1.0.6" - extend "~3.0.2" - forever-agent "~0.6.1" - form-data "~2.3.2" - har-validator "~5.1.3" - http-signature "~1.2.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.19" - oauth-sign "~0.9.0" - performance-now "^2.1.0" - qs "~6.5.2" - safe-buffer "^5.1.2" - tough-cookie "~2.5.0" - tunnel-agent "^0.6.0" - uuid "^3.3.2" - require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== -require-from-string@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" - integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== - -require-main-filename@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" - integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== - requireindex@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/requireindex/-/requireindex-1.2.0.tgz#3463cdb22ee151902635aa6c9535d4de9c2ef1ef" @@ -5915,12 +5420,12 @@ resolve@1.17.0: dependencies: path-parse "^1.0.6" -resolve@^1.1.6, resolve@^1.22.1: - version "1.22.1" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" - integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== +resolve@^1.1.6, resolve@^1.22.4: + version "1.22.8" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" + integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== dependencies: - is-core-module "^2.9.0" + is-core-module "^2.13.0" path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" @@ -5944,13 +5449,6 @@ rimraf@2.6.3: dependencies: glob "^7.1.3" -rimraf@^2.2.8: - version "2.7.1" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" - integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== - dependencies: - glob "^7.1.3" - rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" @@ -5966,7 +5464,7 @@ ripemd160@^2.0.0, ripemd160@^2.0.1: hash-base "^3.0.0" inherits "^2.0.1" -rlp@^2.2.3, rlp@^2.2.4: +rlp@^2.2.3: version "2.2.7" resolved "https://registry.yarnpkg.com/rlp/-/rlp-2.2.7.tgz#33f31c4afac81124ac4b283e2bd4d9720b30beaf" integrity sha512-d5gdPmgQ0Z+AklL2NVXr/IoSjNZFfTVvQWzL/AM2AOcSzYP2xjlb0AC8YyCLc41MSNf6P6QVtjgPdmVtzb+4lQ== @@ -5978,13 +5476,6 @@ run-async@^2.2.0: resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== -run-parallel-limit@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/run-parallel-limit/-/run-parallel-limit-1.1.0.tgz#be80e936f5768623a38a963262d6bef8ff11e7ba" - integrity sha512-jJA7irRNM91jaKc3Hcl1npHsFLOXOoTkPCUL1JEa1R82O2miplXXRaGdjW/KM/98YQWDhJLiSs793CnXfblJUw== - dependencies: - queue-microtask "^1.2.2" - run-parallel@^1.1.9: version "1.2.0" resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" @@ -5992,11 +5483,6 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" -rustbn.js@~0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/rustbn.js/-/rustbn.js-0.2.0.tgz#8082cb886e707155fd1cb6f23bd591ab8d55d0ca" - integrity sha512-4VlvkRUuCJvr2J6Y0ImW7NvTCriMi7ErOAqWk1y69vAdoNIzCF3yPmgeNzx+RQTLEDFq5sHfscn1MwHxP9hNfA== - rxjs@^6.4.0: version "6.6.7" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" @@ -6005,12 +5491,22 @@ rxjs@^6.4.0: tslib "^1.9.0" rxjs@^7.2.0: - version "7.8.0" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.0.tgz#90a938862a82888ff4c7359811a595e14e1e09a4" - integrity sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg== + version "7.8.1" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543" + integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg== dependencies: tslib "^2.1.0" +safe-array-concat@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.2.tgz#81d77ee0c4e8b863635227c721278dd524c20edb" + integrity sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q== + dependencies: + call-bind "^1.0.7" + get-intrinsic "^1.2.4" + has-symbols "^1.0.3" + isarray "^2.0.5" + safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" @@ -6021,16 +5517,16 @@ safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -safe-regex-test@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.0.tgz#793b874d524eb3640d1873aad03596db2d4f2295" - integrity sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA== +safe-regex-test@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.3.tgz#a5b4c0f06e0ab50ea2c395c14d8371232924c377" + integrity sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw== dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.1.3" + call-bind "^1.0.6" + es-errors "^1.3.0" is-regex "^1.1.4" -"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: +"safer-buffer@>= 2.1.2 < 3": version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== @@ -6055,20 +5551,15 @@ sc-istanbul@^0.4.5: which "^1.1.1" wordwrap "^1.0.0" -schema-utils@^3.1.0, schema-utils@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.1.1.tgz#bc74c4b6b6995c1d88f76a8b77bea7219e0c8281" - integrity sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw== +schema-utils@^3.1.1, schema-utils@^3.2.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.3.0.tgz#f50a88877c3c01652a15b622ae9e9795df7a60fe" + integrity sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg== dependencies: "@types/json-schema" "^7.0.8" ajv "^6.12.5" ajv-keywords "^3.5.2" -scrypt-js@2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/scrypt-js/-/scrypt-js-2.0.4.tgz#32f8c5149f0797672e551c07e230f834b6af5f16" - integrity sha512-4KsaGcPnuhtCZQCxFxN3GVYIhKFPTdLd8PLC552XwbMndtD0cjRFAhDuuydXQ0h08ZfPgzqe6EKHozpuH74iDw== - scrypt-js@3.0.1, scrypt-js@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/scrypt-js/-/scrypt-js-3.0.1.tgz#d314a57c2aef69d1ad98a138a21fe9eafa9ee312" @@ -6088,46 +5579,49 @@ seedrandom@^3.0.5: resolved "https://registry.yarnpkg.com/seedrandom/-/seedrandom-3.0.5.tgz#54edc85c95222525b0c7a6f6b3543d8e0b3aa0a7" integrity sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg== -semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.0: - version "5.7.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" - integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== +semver@^5.5.0, semver@^5.5.1, semver@^5.6.0: + version "5.7.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" + integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== -semver@^6.3.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" - integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== +semver@^6.3.0, semver@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8: - version "7.3.8" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798" - integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A== - dependencies: - lru-cache "^6.0.0" +semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.5.4: + version "7.6.3" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" + integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== -serialize-javascript@6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8" - integrity sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag== +serialize-javascript@^6.0.1, serialize-javascript@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz#defa1e055c83bf6d59ea805d8da862254eb6a6c2" + integrity sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g== dependencies: randombytes "^2.1.0" -serialize-javascript@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.1.tgz#b206efb27c3da0b0ab6b52f48d170b7996458e5c" - integrity sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w== +set-function-length@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" + integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== dependencies: - randombytes "^2.1.0" - -set-blocking@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" - integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== + define-data-property "^1.1.4" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + gopd "^1.0.1" + has-property-descriptors "^1.0.2" -setimmediate@1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.4.tgz#20e81de622d4a02588ce0c8da8973cbcf1d3138f" - integrity sha512-/TjEmXQVEzdod/FFskf3o7oOAsGhHf2j1dZqRFbDzq4F3mvvxflIIi4Hd3bLQE9y/CpwqfSQam5JakI/mi3Pog== +set-function-name@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.2.tgz#16a705c5a0dc2f5e638ca96d8a8cd4e1c2b90985" + integrity sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + functions-have-names "^1.2.3" + has-property-descriptors "^1.0.2" setimmediate@^1.0.5: version "1.0.5" @@ -6188,14 +5682,15 @@ shelljs@^0.8.3: interpret "^1.0.0" rechoir "^0.6.2" -side-channel@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" - integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== +side-channel@^1.0.4, side-channel@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" + integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA== dependencies: - call-bind "^1.0.0" - get-intrinsic "^1.0.2" - object-inspect "^1.9.0" + call-bind "^1.0.7" + es-errors "^1.3.0" + get-intrinsic "^1.2.4" + object-inspect "^1.13.1" sigmund@^1.0.1: version "1.0.1" @@ -6221,18 +5716,16 @@ slice-ansi@^2.1.0: astral-regex "^1.0.0" is-fullwidth-code-point "^2.0.0" -solc@0.7.3: - version "0.7.3" - resolved "https://registry.yarnpkg.com/solc/-/solc-0.7.3.tgz#04646961bd867a744f63d2b4e3c0701ffdc7d78a" - integrity sha512-GAsWNAjGzIDg7VxzP6mPjdurby3IkGCjQcM8GFYZT6RyaoUZKmMU6Y7YwG+tFGhv7dwZ8rmR4iwFDrrD99JwqA== +solc@0.8.26: + version "0.8.26" + resolved "https://registry.yarnpkg.com/solc/-/solc-0.8.26.tgz#afc78078953f6ab3e727c338a2fefcd80dd5b01a" + integrity sha512-yiPQNVf5rBFHwN6SIf3TUUvVAFKcQqmSUFeq+fb6pNRCo0ZCgpYOZDi3BVoezCPIAcKrVYd/qXlBLUP9wVrZ9g== dependencies: command-exists "^1.2.8" - commander "3.0.2" + commander "^8.1.0" follow-redirects "^1.12.1" - fs-extra "^0.30.0" js-sha3 "0.8.0" memorystream "^0.3.1" - require-from-string "^2.0.0" semver "^5.5.0" tmp "0.0.33" @@ -6256,29 +5749,28 @@ solhint@^2.0.0: optionalDependencies: prettier "^1.14.3" -solidity-comments-extractor@^0.0.7: - version "0.0.7" - resolved "https://registry.yarnpkg.com/solidity-comments-extractor/-/solidity-comments-extractor-0.0.7.tgz#99d8f1361438f84019795d928b931f4e5c39ca19" - integrity sha512-wciNMLg/Irp8OKGrh3S2tfvZiZ0NEyILfcRCXCD4mp7SgK/i9gzLfhY2hY7VMCQJ3kH9UB9BzNdibIVMchzyYw== +solidity-comments-extractor@^0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/solidity-comments-extractor/-/solidity-comments-extractor-0.0.8.tgz#f6e148ab0c49f30c1abcbecb8b8df01ed8e879f8" + integrity sha512-htM7Vn6LhHreR+EglVMd2s+sZhcXAirB1Zlyrv5zBuTxieCvjfnRpd7iZk75m/u6NOlEyQ94C6TWbBn2cY7w8g== solidity-coverage@^0.8.2: - version "0.8.2" - resolved "https://registry.yarnpkg.com/solidity-coverage/-/solidity-coverage-0.8.2.tgz#bc39604ab7ce0a3fa7767b126b44191830c07813" - integrity sha512-cv2bWb7lOXPE9/SSleDO6czkFiMHgP4NXPj+iW9W7iEKLBk7Cj0AGBiNmGX3V1totl9wjPrT0gHmABZKZt65rQ== + version "0.8.12" + resolved "https://registry.yarnpkg.com/solidity-coverage/-/solidity-coverage-0.8.12.tgz#c4fa2f64eff8ada7a1387b235d6b5b0e6c6985ed" + integrity sha512-8cOB1PtjnjFRqOgwFiD8DaUsYJtVJ6+YdXQtSZDrLGf8cdhhh8xzTtGzVTGeBf15kTv0v7lYPJlV/az7zLEPJw== dependencies: "@ethersproject/abi" "^5.0.9" - "@solidity-parser/parser" "^0.14.1" + "@solidity-parser/parser" "^0.18.0" chalk "^2.4.2" death "^1.1.0" - detect-port "^1.3.0" difflib "^0.2.4" fs-extra "^8.1.0" ghost-testrpc "^0.0.2" global-modules "^2.0.0" globby "^10.0.1" jsonschema "^1.2.4" - lodash "^4.17.15" - mocha "7.1.2" + lodash "^4.17.21" + mocha "^10.2.0" node-emoji "^1.10.0" pify "^4.0.1" recursive-readdir "^2.2.2" @@ -6287,10 +5779,10 @@ solidity-coverage@^0.8.2: shelljs "^0.8.3" web3-utils "^1.3.6" -source-map-js@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" - integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== +source-map-js@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af" + integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg== source-map-support@^0.5.13, source-map-support@~0.5.20: version "0.5.21" @@ -6317,21 +5809,6 @@ sprintf-js@~1.0.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== -sshpk@^1.7.0: - version "1.17.0" - resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.17.0.tgz#578082d92d4fe612b13007496e543fa0fbcbe4c5" - integrity sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ== - dependencies: - asn1 "~0.2.3" - assert-plus "^1.0.0" - bcrypt-pbkdf "^1.0.0" - dashdash "^1.12.0" - ecc-jsbn "~0.1.1" - getpass "^0.1.1" - jsbn "~0.1.0" - safer-buffer "^2.0.2" - tweetnacl "~0.14.0" - stacktrace-parser@^0.1.10: version "0.1.10" resolved "https://registry.yarnpkg.com/stacktrace-parser/-/stacktrace-parser-0.1.10.tgz#29fb0cae4e0d0b85155879402857a1639eb6051a" @@ -6344,11 +5821,6 @@ statuses@2.0.1: resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== -stealthy-require@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b" - integrity sha512-ZnWpYnYugiOVEY5GkcuJK1io5V8QmNYChG62gSit9pQVGErXtrKuPC55ITaVSukmMta5qpMU7vqLt2Lnni4f/g== - stop-iteration-iterator@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz#6a60be0b4ee757d1ed5254858ec66b10c49285e4" @@ -6356,17 +5828,12 @@ stop-iteration-iterator@^1.0.0: dependencies: internal-slot "^1.0.4" -streamsearch@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764" - integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== - string-format@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/string-format/-/string-format-2.0.0.tgz#f2df2e7097440d3b65de31b6d40d54c96eaffb9b" integrity sha512-bbEs3scLeYNXLecRRuk6uJxdXUSj6le/8rNPHChIJTn2V79aXVTR1EH2OH5zLKKoz0V02fOUKZZcw01pLUShZA== -"string-width@^1.0.2 || 2", string-width@^2.1.0, string-width@^2.1.1: +string-width@^2.1.0, string-width@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== @@ -6374,7 +5841,7 @@ string-format@^2.0.0: is-fullwidth-code-point "^2.0.0" strip-ansi "^4.0.0" -string-width@^3.0.0, string-width@^3.1.0: +string-width@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== @@ -6383,7 +5850,7 @@ string-width@^3.0.0, string-width@^3.1.0: is-fullwidth-code-point "^2.0.0" strip-ansi "^5.1.0" -string-width@^4.1.0, string-width@^4.2.0: +string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -6392,32 +5859,41 @@ string-width@^4.1.0, string-width@^4.2.0: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" -string.prototype.trim@^1.2.7: - version "1.2.7" - resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz#a68352740859f6893f14ce3ef1bb3037f7a90533" - integrity sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg== +string.prototype.includes@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/string.prototype.includes/-/string.prototype.includes-2.0.0.tgz#8986d57aee66d5460c144620a6d873778ad7289f" + integrity sha512-E34CkBgyeqNDcrbU76cDjL5JLcVrtSdYq0MEh/B10r17pRP4ciHLwTgnuLV8Ay6cgEMLkcBkFCKyFZ43YldYzg== dependencies: - call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" + define-properties "^1.1.3" + es-abstract "^1.17.5" -string.prototype.trimend@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz#c4a27fa026d979d79c04f17397f250a462944533" - integrity sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ== +string.prototype.trim@^1.2.9: + version "1.2.9" + resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz#b6fa326d72d2c78b6df02f7759c73f8f6274faa4" + integrity sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw== dependencies: - call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.0" + es-object-atoms "^1.0.0" -string.prototype.trimstart@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz#e90ab66aa8e4007d92ef591bbf3cd422c56bdcf4" - integrity sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA== +string.prototype.trimend@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz#3651b8513719e8a9f48de7f2f77640b26652b229" + integrity sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ== dependencies: - call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" + call-bind "^1.0.7" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + +string.prototype.trimstart@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz#7ee834dda8c7c17eff3118472bb35bfedaa34dde" + integrity sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" string_decoder@^1.1.1: version "1.3.0" @@ -6440,7 +5916,7 @@ strip-ansi@^4.0.0: dependencies: ansi-regex "^3.0.0" -strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: +strip-ansi@^5.1.0: version "5.2.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== @@ -6466,30 +5942,16 @@ strip-hex-prefix@1.0.0: dependencies: is-hex-prefixed "1.0.0" -strip-json-comments@2.0.1, strip-json-comments@^2.0.1: +strip-json-comments@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== -strip-json-comments@3.1.1, strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: +strip-json-comments@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== -supports-color@6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.0.0.tgz#76cfe742cf1f41bb9b1c29ad03068c05b4c0e40a" - integrity sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg== - dependencies: - has-flag "^3.0.0" - -supports-color@8.1.1, supports-color@^8.0.0: - version "8.1.1" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" - integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== - dependencies: - has-flag "^4.0.0" - supports-color@^3.1.0: version "3.2.3" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6" @@ -6511,6 +5973,13 @@ supports-color@^7.1.0: dependencies: has-flag "^4.0.0" +supports-color@^8.0.0, supports-color@^8.1.1: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + supports-preserve-symlinks-flag@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" @@ -6557,24 +6026,24 @@ tapable@^2.0.0, tapable@^2.1.1, tapable@^2.2.0: resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== -terser-webpack-plugin@^5.1.3: - version "5.3.7" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.7.tgz#ef760632d24991760f339fe9290deb936ad1ffc7" - integrity sha512-AfKwIktyP7Cu50xNjXF/6Qb5lBNzYaWpU6YfoX3uZicTx0zTy0stDDCsvjDapKsSDvOeWo5MEq4TmdBy2cNoHw== +terser-webpack-plugin@^5.3.10: + version "5.3.10" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz#904f4c9193c6fd2a03f693a2150c62a92f40d199" + integrity sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w== dependencies: - "@jridgewell/trace-mapping" "^0.3.17" + "@jridgewell/trace-mapping" "^0.3.20" jest-worker "^27.4.5" schema-utils "^3.1.1" serialize-javascript "^6.0.1" - terser "^5.16.5" + terser "^5.26.0" -terser@^5.10.0, terser@^5.16.5: - version "5.16.8" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.16.8.tgz#ccde583dabe71df3f4ed02b65eb6532e0fae15d5" - integrity sha512-QI5g1E/ef7d+PsDifb+a6nnVgC4F22Bg6T0xrBrz6iloVB4PUkkunp6V8nzoOOZJIzjWVdAGqCdlKlhLq/TbIA== +terser@^5.10.0, terser@^5.26.0: + version "5.31.3" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.31.3.tgz#b24b7beb46062f4653f049eea4f0cd165d0f0c38" + integrity sha512-pAfYn3NIZLyZpa83ZKigvj6Rn9c/vd5KfYGX7cN1mnzqgDcxWvrU5ZtAfIKhEXz9nRecw4z3LXkjaq96/qZqAA== dependencies: - "@jridgewell/source-map" "^0.3.2" - acorn "^8.5.0" + "@jridgewell/source-map" "^0.3.3" + acorn "^8.8.2" commander "^2.20.0" source-map-support "~0.5.20" @@ -6629,20 +6098,11 @@ toidentifier@1.0.1: resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== -tough-cookie@^2.3.3, tough-cookie@~2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" - integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== - dependencies: - psl "^1.1.28" - punycode "^2.1.1" - ts-command-line-args@^2.2.0: - version "2.4.2" - resolved "https://registry.yarnpkg.com/ts-command-line-args/-/ts-command-line-args-2.4.2.tgz#b4815b23c35f8a0159d4e69e01012d95690bc448" - integrity sha512-mJLQQBOdyD4XI/ZWQY44PIdYde47JhV2xl380O7twPkTQ+Y5vFDHsk8LOeXKuz7dVY5aDCfAzRarNfSqtKOkQQ== + version "2.5.1" + resolved "https://registry.yarnpkg.com/ts-command-line-args/-/ts-command-line-args-2.5.1.tgz#e64456b580d1d4f6d948824c274cf6fa5f45f7f0" + integrity sha512-H69ZwTw3rFHb5WYpQya40YAX2/w7Ut75uUECbgBIsLmM+BNuYnxsltfyyLMxy6sEeKxgijLTnQtLd0nKd6+IYw== dependencies: - "@morgan-stanley/ts-mocking-bird" "^0.6.2" chalk "^4.1.0" command-line-args "^5.1.1" command-line-usage "^6.1.0" @@ -6654,9 +6114,9 @@ ts-essentials@^7.0.1: integrity sha512-8+gr5+lqO3G84KdiTSMRLtuyJ+nTBVRKuCrK4lidMPdVeEp0uqC875uE5NMcaA7YYMN7XsNiFQuMvasF8HT/xQ== ts-node@>=8.0.0: - version "10.9.1" - resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.1.tgz#e73de9102958af9e1f0b168a6ff320e25adcff4b" - integrity sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw== + version "10.9.2" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f" + integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== dependencies: "@cspotcode/source-map-support" "^0.8.0" "@tsconfig/node10" "^1.0.7" @@ -6672,10 +6132,10 @@ ts-node@>=8.0.0: v8-compile-cache-lib "^3.0.1" yn "3.1.1" -tsconfig-paths@^3.14.1: - version "3.14.2" - resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz#6e32f1f79412decd261f92d633a9dc1cfa99f088" - integrity sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g== +tsconfig-paths@^3.15.0: + version "3.15.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz#5299ec605e55b1abb23ec939ef15edaf483070d4" + integrity sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg== dependencies: "@types/json5" "^0.0.29" json5 "^1.0.2" @@ -6688,9 +6148,9 @@ tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== tslib@^2.0.3, tslib@^2.1.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.0.tgz#42bfed86f5787aeb41d031866c8f402429e0fddf" - integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg== + version "2.6.3" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.3.tgz#0438f810ad7a9edcde7a241c3d80db693c8cbfe0" + integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ== tsort@0.0.1: version "0.0.1" @@ -6704,23 +6164,11 @@ tsutils@^3.21.0: dependencies: tslib "^1.8.1" -tunnel-agent@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" - integrity sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w== - dependencies: - safe-buffer "^5.0.1" - tweetnacl-util@^0.15.1: version "0.15.1" resolved "https://registry.yarnpkg.com/tweetnacl-util/-/tweetnacl-util-0.15.1.tgz#b80fcdb5c97bcc508be18c44a4be50f022eea00b" integrity sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw== -tweetnacl@^0.14.3, tweetnacl@~0.14.0: - version "0.14.5" - resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" - integrity sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA== - tweetnacl@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-1.0.3.tgz#ac0af71680458d8a6378d0d0d050ab1407d35596" @@ -6740,10 +6188,10 @@ type-check@~0.3.2: dependencies: prelude-ls "~1.1.2" -type-detect@^4.0.0, type-detect@^4.0.5: - version "4.0.8" - resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" - integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== +type-detect@^4.0.0, type-detect@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.1.0.tgz#deb2453e8f08dcae7ae98c626b13dddb0155906c" + integrity sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw== type-fest@^0.20.2: version "0.20.2" @@ -6761,9 +6209,9 @@ type-fest@^0.7.1: integrity sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg== typechain@^8.1.1: - version "8.1.1" - resolved "https://registry.yarnpkg.com/typechain/-/typechain-8.1.1.tgz#9c2e8012c2c4c586536fc18402dcd7034c4ff0bd" - integrity sha512-uF/sUvnXTOVF2FHKhQYnxHk4su4JjZR8vr4mA2mBaRwHTbwh0jIlqARz9XJr1tA0l7afJGvEa1dTSi4zt039LQ== + version "8.3.2" + resolved "https://registry.yarnpkg.com/typechain/-/typechain-8.3.2.tgz#1090dd8d9c57b6ef2aed3640a516bdbf01b00d73" + integrity sha512-x/sQYr5w9K7yv3es7jo4KTX05CLxOf7TRWwoHlrjRh8H82G64g+k7VuWPJlgMo6qrjfCulOdfBjiaDtmhFYD/Q== dependencies: "@types/prettier" "^2.1.1" debug "^4.3.1" @@ -6776,14 +6224,49 @@ typechain@^8.1.1: ts-command-line-args "^2.2.0" ts-essentials "^7.0.1" -typed-array-length@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.4.tgz#89d83785e5c4098bec72e08b319651f0eac9c1bb" - integrity sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng== +typed-array-buffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz#1867c5d83b20fcb5ccf32649e5e2fc7424474ff3" + integrity sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ== dependencies: - call-bind "^1.0.2" + call-bind "^1.0.7" + es-errors "^1.3.0" + is-typed-array "^1.1.13" + +typed-array-byte-length@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz#d92972d3cff99a3fa2e765a28fcdc0f1d89dec67" + integrity sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw== + dependencies: + call-bind "^1.0.7" for-each "^0.3.3" - is-typed-array "^1.1.9" + gopd "^1.0.1" + has-proto "^1.0.3" + is-typed-array "^1.1.13" + +typed-array-byte-offset@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz#f9ec1acb9259f395093e4567eb3c28a580d02063" + integrity sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA== + dependencies: + available-typed-arrays "^1.0.7" + call-bind "^1.0.7" + for-each "^0.3.3" + gopd "^1.0.1" + has-proto "^1.0.3" + is-typed-array "^1.1.13" + +typed-array-length@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.6.tgz#57155207c76e64a3457482dfdc1c9d1d3c4c73a3" + integrity sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g== + dependencies: + call-bind "^1.0.7" + for-each "^0.3.3" + gopd "^1.0.1" + has-proto "^1.0.3" + is-typed-array "^1.1.13" + possible-typed-array-names "^1.0.0" typed-function@^2.1.0: version "2.1.0" @@ -6804,9 +6287,9 @@ typescript-formatter@^7.2.2: editorconfig "^0.15.0" typescript@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.0.2.tgz#891e1a90c5189d8506af64b9ef929fca99ba1ee5" - integrity sha512-wVORMBGO/FAs/++blGNeAVdbNKtIh1rbBL2EyQ1+J9lClJ93KiiKe8PmFIVdXhHcyv44SL9oglmfeSsndo0jRw== + version "5.5.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.4.tgz#d9852d6c82bad2d2eda4fd74a5762a8f5909e9ba" + integrity sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q== typical@^4.0.0: version "4.0.0" @@ -6819,9 +6302,9 @@ typical@^5.2.0: integrity sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg== uglify-js@^3.1.4: - version "3.17.4" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.17.4.tgz#61678cf5fa3f5b7eb789bb345df29afb8257c22c" - integrity sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g== + version "3.19.1" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.19.1.tgz#2d5df6a0872c43da43187968308d7741d44b8056" + integrity sha512-y/2wiW+ceTYR2TSSptAhfnEtpLaQ4Ups5zrjB2d3kuVxHj16j/QJwPl5PvuGy9uARb39J0+iKxcRPvtpsx4A4A== unbox-primitive@^1.0.2: version "1.0.2" @@ -6833,12 +6316,17 @@ unbox-primitive@^1.0.2: has-symbols "^1.0.3" which-boxed-primitive "^1.0.2" +undici-types@~6.11.1: + version "6.11.1" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.11.1.tgz#432ea6e8efd54a48569705a699e62d8f4981b197" + integrity sha512-mIDEX2ek50x0OlRgxryxsenE5XaQD4on5U2inY7RApK3SOJpofyw7uW2AyfMKkhAxXIceo2DeWGVGwyvng1GNQ== + undici@^5.14.0: - version "5.21.0" - resolved "https://registry.yarnpkg.com/undici/-/undici-5.21.0.tgz#b00dfc381f202565ab7f52023222ab862bb2494f" - integrity sha512-HOjK8l6a57b2ZGXOcUsI5NLfoTrfmbOl90ixJDl0AEFG4wgHNDQxtZy15/ZQp7HhjkpaGlp/eneMgtsu1dIlUA== + version "5.28.4" + resolved "https://registry.yarnpkg.com/undici/-/undici-5.28.4.tgz#6b280408edb6a1a604a9b20340f45b422e373068" + integrity sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g== dependencies: - busboy "^1.6.0" + "@fastify/busboy" "^2.0.0" universalify@^0.1.0: version "0.1.2" @@ -6846,22 +6334,22 @@ universalify@^0.1.0: integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== universalify@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" - integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== + version "2.0.1" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d" + integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== unpipe@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== -update-browserslist-db@^1.0.10: - version "1.0.10" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz#0f54b876545726f17d00cd9a2561e6dade943ff3" - integrity sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ== +update-browserslist-db@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz#7ca61c0d8650766090728046e416a8cde682859e" + integrity sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ== dependencies: - escalade "^3.1.1" - picocolors "^1.0.0" + escalade "^3.1.2" + picocolors "^1.0.1" uri-js@^4.2.2: version "4.4.1" @@ -6885,21 +6373,6 @@ utila@~0.4: resolved "https://registry.yarnpkg.com/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c" integrity sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA== -uuid@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.1.tgz#c2a30dedb3e535d72ccf82e343941a50ba8533ac" - integrity sha512-nWg9+Oa3qD2CQzHIP4qKUqwNfzKn8P0LtFhotaCTFchsV7ZfDhAybeip/HZVeMIpZi9JgY1E3nUlwaCmZT1sEg== - -uuid@^3.3.2: - version "3.4.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" - integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== - -uuid@^7.0.3: - version "7.0.3" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-7.0.3.tgz#c5c9f2c8cf25dc0a372c4df1441c41f5bd0c680b" - integrity sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg== - uuid@^8.3.2: version "8.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" @@ -6910,24 +6383,15 @@ v8-compile-cache-lib@^3.0.1: resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== -verror@1.10.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" - integrity sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw== - dependencies: - assert-plus "^1.0.0" - core-util-is "1.0.2" - extsprintf "^1.2.0" - vue-hot-reload-api@^2.3.0: version "2.3.4" resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz#532955cc1eb208a3d990b3a9f9a70574657e08f2" integrity sha512-BXq3jwIagosjgNVae6tkHzzIk6a8MHFtzAdwhnV5VlvPTFxDCvIttgSiHWjdGoTJvXtmRu5HacExfdarRcFhog== vue-loader@^15.9.8: - version "15.10.1" - resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-15.10.1.tgz#c451c4cd05a911aae7b5dbbbc09fb913fb3cca18" - integrity sha512-SaPHK1A01VrNthlix6h1hq4uJu7S/z0kdLUb6klubo738NeQoLbS6V9/d8Pv19tU0XdQKju3D1HSKuI8wJ5wMA== + version "15.11.1" + resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-15.11.1.tgz#dee91169211276ed43c5715caef88a56b1f497b0" + integrity sha512-0iw4VchYLePqJfJu9s62ACWUXeSqM30SQqlIftbYWM3C+jpPcEHKSPUZBLjSF9au4HTHQ/naF6OGnO3Q/qGR3Q== dependencies: "@vue/component-compiler-utils" "^3.1.0" hash-sum "^1.0.2" @@ -6949,9 +6413,9 @@ vue-style-loader@^4.1.0: loader-utils "^1.0.2" vue-template-compiler@^2.6.14: - version "2.7.14" - resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.7.14.tgz#4545b7dfb88090744c1577ae5ac3f964e61634b1" - integrity sha512-zyA5Y3ArvVG0NacJDkkzJuPQDF8RFeRlzV2vLeSnhSpieO6LK2OVbdLPi5MPPs09Ii+gMO8nY4S3iKQxBxDmWQ== + version "2.7.16" + resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.7.16.tgz#c81b2d47753264c77ac03b9966a46637482bb03b" + integrity sha512-AYbUWAJHLGGQM7+cNTELw+KsOG9nl2CnSv467WobS5Cv9uk3wFcnr1Etsz2sEIHEZvw1U+o9mRlEO6QbZvUPGQ== dependencies: de-indent "^1.0.2" he "^1.2.0" @@ -6962,29 +6426,30 @@ vue-template-es2015-compiler@^1.9.0: integrity sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw== vue@^2.6.14: - version "2.7.14" - resolved "https://registry.yarnpkg.com/vue/-/vue-2.7.14.tgz#3743dcd248fd3a34d421ae456b864a0246bafb17" - integrity sha512-b2qkFyOM0kwqWFuQmgd4o+uHGU7T+2z3T+WQp8UBjADfEv2n4FEMffzBmCKNP0IGzOEEfYjvtcC62xaSKeQDrQ== + version "2.7.16" + resolved "https://registry.yarnpkg.com/vue/-/vue-2.7.16.tgz#98c60de9def99c0e3da8dae59b304ead43b967c9" + integrity sha512-4gCtFXaAA3zYZdTp5s4Hl2sozuySsgz4jy1EnpBHNfpMa9dK1ZCG7viqBPCwXtmgc8nHqUsAu3G4gtmXkkY3Sw== dependencies: - "@vue/compiler-sfc" "2.7.14" + "@vue/compiler-sfc" "2.7.16" csstype "^3.1.0" -watchpack@^2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d" - integrity sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg== +watchpack@^2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.1.tgz#29308f2cac150fa8e4c92f90e0ec954a9fed7fff" + integrity sha512-8wrBCMtVhqcXP2Sup1ctSkga6uc2Bx0IIvKyT7yTFier5AXHooSI+QyQQAtTb7+E0IUCCKyTFmXqdqgum2XWGg== dependencies: glob-to-regexp "^0.4.1" graceful-fs "^4.1.2" web3-utils@^1.3.6: - version "1.10.0" - resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-1.10.0.tgz#ca4c1b431a765c14ac7f773e92e0fd9377ccf578" - integrity sha512-kSaCM0uMcZTNUSmn5vMEhlo02RObGNRRCkdX0V9UTAU0+lrvn0HSaudyCo6CQzuXUsnuY2ERJGCGPfeWmv19Rg== + version "1.10.4" + resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-1.10.4.tgz#0daee7d6841641655d8b3726baf33b08eda1cbec" + integrity sha512-tsu8FiKJLk2PzhDl9fXbGUWTkkVXYhtTA+SmEFkKft+9BgwLxfCRpU96sWv7ICC8zixBNd3JURVoiR3dUXgP8A== dependencies: + "@ethereumjs/util" "^8.1.0" bn.js "^5.2.1" ethereum-bloom-filters "^1.0.6" - ethereumjs-util "^7.1.0" + ethereum-cryptography "^2.1.2" ethjs-unit "0.1.6" number-to-bn "1.7.0" randombytes "^2.1.0" @@ -6996,33 +6461,33 @@ webpack-sources@^3.2.3: integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== webpack@^5.65.0: - version "5.76.3" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.76.3.tgz#dffdc72c8950e5b032fddad9c4452e7787d2f489" - integrity sha512-18Qv7uGPU8b2vqGeEEObnfICyw2g39CHlDEK4I7NK13LOur1d0HGmGNKGT58Eluwddpn3oEejwvBPoP4M7/KSA== + version "5.93.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.93.0.tgz#2e89ec7035579bdfba9760d26c63ac5c3462a5e5" + integrity sha512-Y0m5oEY1LRuwly578VqluorkXbvXKh7U3rLoQCEO04M97ScRr44afGVkI0FQFsXzysk5OgFAxjZAb9rsGQVihA== dependencies: "@types/eslint-scope" "^3.7.3" - "@types/estree" "^0.0.51" - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/wasm-edit" "1.11.1" - "@webassemblyjs/wasm-parser" "1.11.1" + "@types/estree" "^1.0.5" + "@webassemblyjs/ast" "^1.12.1" + "@webassemblyjs/wasm-edit" "^1.12.1" + "@webassemblyjs/wasm-parser" "^1.12.1" acorn "^8.7.1" - acorn-import-assertions "^1.7.6" - browserslist "^4.14.5" + acorn-import-attributes "^1.9.5" + browserslist "^4.21.10" chrome-trace-event "^1.0.2" - enhanced-resolve "^5.10.0" - es-module-lexer "^0.9.0" + enhanced-resolve "^5.17.0" + es-module-lexer "^1.2.1" eslint-scope "5.1.1" events "^3.2.0" glob-to-regexp "^0.4.1" - graceful-fs "^4.2.9" + graceful-fs "^4.2.11" json-parse-even-better-errors "^2.3.1" loader-runner "^4.2.0" mime-types "^2.1.27" neo-async "^2.6.2" - schema-utils "^3.1.0" + schema-utils "^3.2.0" tapable "^2.1.1" - terser-webpack-plugin "^5.1.3" - watchpack "^2.4.0" + terser-webpack-plugin "^5.3.10" + watchpack "^2.4.1" webpack-sources "^3.2.3" which-boxed-primitive@^1.0.2: @@ -7036,34 +6501,46 @@ which-boxed-primitive@^1.0.2: is-string "^1.0.5" is-symbol "^1.0.3" -which-collection@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.1.tgz#70eab71ebbbd2aefaf32f917082fc62cdcb70906" - integrity sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A== +which-builtin-type@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/which-builtin-type/-/which-builtin-type-1.1.3.tgz#b1b8443707cc58b6e9bf98d32110ff0c2cbd029b" + integrity sha512-YmjsSMDBYsM1CaFiayOVT06+KJeXf0o5M/CAd4o1lTadFAtacTUM49zoYxr/oroopFDfhvN6iEcBxUyc3gvKmw== dependencies: - is-map "^2.0.1" - is-set "^2.0.1" - is-weakmap "^2.0.1" - is-weakset "^2.0.1" + function.prototype.name "^1.1.5" + has-tostringtag "^1.0.0" + is-async-function "^2.0.0" + is-date-object "^1.0.5" + is-finalizationregistry "^1.0.2" + is-generator-function "^1.0.10" + is-regex "^1.1.4" + is-weakref "^1.0.2" + isarray "^2.0.5" + which-boxed-primitive "^1.0.2" + which-collection "^1.0.1" + which-typed-array "^1.1.9" -which-module@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" - integrity sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q== +which-collection@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.2.tgz#627ef76243920a107e7ce8e96191debe4b16c2a0" + integrity sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw== + dependencies: + is-map "^2.0.3" + is-set "^2.0.3" + is-weakmap "^2.0.2" + is-weakset "^2.0.3" -which-typed-array@^1.1.9: - version "1.1.9" - resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.9.tgz#307cf898025848cf995e795e8423c7f337efbde6" - integrity sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA== +which-typed-array@^1.1.13, which-typed-array@^1.1.14, which-typed-array@^1.1.15, which-typed-array@^1.1.9: + version "1.1.15" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.15.tgz#264859e9b11a649b388bfaaf4f767df1f779b38d" + integrity sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA== dependencies: - available-typed-arrays "^1.0.5" - call-bind "^1.0.2" + available-typed-arrays "^1.0.7" + call-bind "^1.0.7" for-each "^0.3.3" gopd "^1.0.1" - has-tostringtag "^1.0.0" - is-typed-array "^1.1.10" + has-tostringtag "^1.0.2" -which@1.3.1, which@^1.1.1, which@^1.2.9, which@^1.3.1: +which@^1.1.1, which@^1.2.9, which@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== @@ -7077,17 +6554,17 @@ which@^2.0.1: dependencies: isexe "^2.0.0" -wide-align@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" - integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== +widest-line@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-3.1.0.tgz#8292333bbf66cb45ff0de1603b136b7ae1496eca" + integrity sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg== dependencies: - string-width "^1.0.2 || 2" + string-width "^4.0.0" -word-wrap@^1.2.3, word-wrap@~1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" - integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== +word-wrap@^1.2.5, word-wrap@~1.2.3: + version "1.2.5" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" + integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== wordwrap@^1.0.0: version "1.0.0" @@ -7102,19 +6579,10 @@ wordwrapjs@^4.0.0: reduce-flatten "^2.0.0" typical "^5.2.0" -workerpool@6.2.1: - version "6.2.1" - resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343" - integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw== - -wrap-ansi@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" - integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q== - dependencies: - ansi-styles "^3.2.0" - string-width "^3.0.0" - strip-ansi "^5.0.0" +workerpool@^6.5.1: + version "6.5.1" + resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.5.1.tgz#060f73b39d0caf97c6db64da004cd01b4c099544" + integrity sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA== wrap-ansi@^7.0.0: version "7.0.0" @@ -7143,19 +6611,9 @@ ws@7.4.6: integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== ws@^7.4.6: - version "7.5.9" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" - integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== - -xmlhttprequest@1.8.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz#67fe075c5c24fef39f9d65f5f7b7fe75171968fc" - integrity sha512-58Im/U0mlVBLM38NdZjHyhuMtCqa61469k2YP/AaPbvCoV9aQGUpbJBj1QRm2ytRiVQBD/fsw7L2bJGDVQswBA== - -y18n@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf" - integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ== + version "7.5.10" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.10.tgz#58b5c20dc281633f6c19113f39b349bd8bd558d9" + integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ== y18n@^5.0.5: version "5.0.8" @@ -7167,44 +6625,12 @@ yallist@^2.1.2: resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" integrity sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A== -yallist@^3.0.2: - version "3.1.1" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" - integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== - -yallist@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" - integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== - -yargs-parser@13.1.2, yargs-parser@^13.1.2: - version "13.1.2" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38" - integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg== - dependencies: - camelcase "^5.0.0" - decamelize "^1.2.0" - -yargs-parser@20.2.4: - version "20.2.4" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" - integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== - -yargs-parser@^20.2.2: +yargs-parser@^20.2.2, yargs-parser@^20.2.9: version "20.2.9" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== -yargs-unparser@1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-1.6.0.tgz#ef25c2c769ff6bd09e4b0f9d7c605fb27846ea9f" - integrity sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw== - dependencies: - flat "^4.1.0" - lodash "^4.17.15" - yargs "^13.3.0" - -yargs-unparser@2.0.0: +yargs-unparser@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-2.0.0.tgz#f131f9226911ae5d9ad38c432fe809366c2325eb" integrity sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA== @@ -7214,23 +6640,7 @@ yargs-unparser@2.0.0: flat "^5.0.2" is-plain-obj "^2.1.0" -yargs@13.3.2, yargs@^13.3.0: - version "13.3.2" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd" - integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw== - dependencies: - cliui "^5.0.0" - find-up "^3.0.0" - get-caller-file "^2.0.1" - require-directory "^2.1.1" - require-main-filename "^2.0.0" - set-blocking "^2.0.0" - string-width "^3.0.0" - which-module "^2.0.0" - y18n "^4.0.0" - yargs-parser "^13.1.2" - -yargs@16.2.0: +yargs@^16.2.0: version "16.2.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== @@ -7253,7 +6663,7 @@ yocto-queue@^0.1.0: resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== -zksync-web3@^0.8.1: - version "0.8.1" - resolved "https://registry.yarnpkg.com/zksync-web3/-/zksync-web3-0.8.1.tgz#db289d8f6caf61f4d5ddc471fa3448d93208dc14" - integrity sha512-1A4aHPQ3MyuGjpv5X/8pVEN+MdZqMjfVmiweQSRjOlklXYu65wT9BGEOtCmMs5d3gIvLp4ssfTeuR5OCKOD2kw== +zksync-web3@^0.14.3: + version "0.14.4" + resolved "https://registry.yarnpkg.com/zksync-web3/-/zksync-web3-0.14.4.tgz#0b70a7e1a9d45cc57c0971736079185746d46b1f" + integrity sha512-kYehMD/S6Uhe1g434UnaMN+sBr9nQm23Ywn0EUP5BfQCsbjcr3ORuS68PosZw8xUTu3pac7G6YMSnNHk+fwzvg== From 0c404593f6f3cc2f2a76bfd3cf72616bc95a76b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20S=C5=82omnicki?= Date: Fri, 19 Jul 2024 10:31:17 +0200 Subject: [PATCH 011/241] Use local IPFS gateway for test envs --- ci/argocd/templates/octant-application.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ci/argocd/templates/octant-application.yaml b/ci/argocd/templates/octant-application.yaml index 6cde872184..dfd7d9ffd2 100644 --- a/ci/argocd/templates/octant-application.yaml +++ b/ci/argocd/templates/octant-application.yaml @@ -33,6 +33,8 @@ spec: value: '$NETWORK_NAME' - name: 'webClient.hideCurrentProjectsOutsideAW' value: 'false' + - name: 'webClient.ipfsGateways' + value: 'https://ipfs.octant.wildland.dev/ipfs/' ## Graph Node - name: graphNode.graph.env.NETWORK value: '$NETWORK_NAME' From 00d89574f3b239f37e0ba1c7c49eea9a38c378c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Kluczek?= Date: Fri, 2 Aug 2024 01:35:09 +0200 Subject: [PATCH 012/241] OCT-1842: Reduce sentry events flood --- backend/app/infrastructure/apscheduler.py | 6 +++++- backend/startup.py | 23 +++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/backend/app/infrastructure/apscheduler.py b/backend/app/infrastructure/apscheduler.py index 35430ba1f0..fdda7c39e8 100644 --- a/backend/app/infrastructure/apscheduler.py +++ b/backend/app/infrastructure/apscheduler.py @@ -1,5 +1,6 @@ from flask import current_app as app +from app import exceptions from app.legacy.core import glm from app.legacy.core import vault from app.extensions import scheduler @@ -16,7 +17,10 @@ def vault_confirm_withdrawals(): with scheduler.app.app_context(): if app.config["VAULT_CONFIRM_WITHDRAWALS_ENABLED"]: app.logger.debug("Confirming withdrawals in Vault contract...") - vault.confirm_withdrawals() + try: + vault.confirm_withdrawals() + except exceptions.MissingSnapshot: + app.logger.warning("No pending snapshot found") @scheduler.task( diff --git a/backend/startup.py b/backend/startup.py index f100cc84d9..861c38017a 100644 --- a/backend/startup.py +++ b/backend/startup.py @@ -20,12 +20,35 @@ if os.getenv("SENTRY_DSN"): import sentry_sdk + def sentry_before_send(event, hint): + exceptions = event.get("exception", []) + if not exceptions: + return event + + exc = exceptions[-1] + mechanism = exc.get("mechanism", {}) + + if mechanism.get("handled"): + return None + + return event + exceptions = event["exception"] + if exceptions: + exc = exceptions[-1] + mechanism = exc.get("mechanism") + if mechanism: + if mechanism.get("handled"): + return None + + return event + print("[+] Starting sentry") sentry_sdk.init( traces_sample_rate=1.0, profiles_sample_rate=1.0, enable_tracing=True, + before_send=sentry_before_send, ) from app import create_app # noqa From 3ecfda0ec6ed63812a2c0f5f9ea1122e3f17a1c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Kluczek?= Date: Sun, 4 Aug 2024 17:31:06 +0200 Subject: [PATCH 013/241] Update wording --- backend/app/infrastructure/apscheduler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/app/infrastructure/apscheduler.py b/backend/app/infrastructure/apscheduler.py index fdda7c39e8..223d97f7ca 100644 --- a/backend/app/infrastructure/apscheduler.py +++ b/backend/app/infrastructure/apscheduler.py @@ -20,7 +20,7 @@ def vault_confirm_withdrawals(): try: vault.confirm_withdrawals() except exceptions.MissingSnapshot: - app.logger.warning("No pending snapshot found") + app.logger.warning("No snapshot found") @scheduler.task( From 9fcf0a4dd2cd7d0899958c8eeafc50e4d63889dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Kluczek?= Date: Mon, 5 Aug 2024 11:06:02 +0200 Subject: [PATCH 014/241] Revert "OCT-1842: Reduce sentry events flood" This reverts commit 6bf0225cb647632f4350dca15f2e0621f8960719. --- backend/app/infrastructure/apscheduler.py | 6 +----- backend/startup.py | 23 ----------------------- 2 files changed, 1 insertion(+), 28 deletions(-) diff --git a/backend/app/infrastructure/apscheduler.py b/backend/app/infrastructure/apscheduler.py index 223d97f7ca..35430ba1f0 100644 --- a/backend/app/infrastructure/apscheduler.py +++ b/backend/app/infrastructure/apscheduler.py @@ -1,6 +1,5 @@ from flask import current_app as app -from app import exceptions from app.legacy.core import glm from app.legacy.core import vault from app.extensions import scheduler @@ -17,10 +16,7 @@ def vault_confirm_withdrawals(): with scheduler.app.app_context(): if app.config["VAULT_CONFIRM_WITHDRAWALS_ENABLED"]: app.logger.debug("Confirming withdrawals in Vault contract...") - try: - vault.confirm_withdrawals() - except exceptions.MissingSnapshot: - app.logger.warning("No snapshot found") + vault.confirm_withdrawals() @scheduler.task( diff --git a/backend/startup.py b/backend/startup.py index 861c38017a..f100cc84d9 100644 --- a/backend/startup.py +++ b/backend/startup.py @@ -20,35 +20,12 @@ if os.getenv("SENTRY_DSN"): import sentry_sdk - def sentry_before_send(event, hint): - exceptions = event.get("exception", []) - if not exceptions: - return event - - exc = exceptions[-1] - mechanism = exc.get("mechanism", {}) - - if mechanism.get("handled"): - return None - - return event - exceptions = event["exception"] - if exceptions: - exc = exceptions[-1] - mechanism = exc.get("mechanism") - if mechanism: - if mechanism.get("handled"): - return None - - return event - print("[+] Starting sentry") sentry_sdk.init( traces_sample_rate=1.0, profiles_sample_rate=1.0, enable_tracing=True, - before_send=sentry_before_send, ) from app import create_app # noqa From c2bfadebdc653448d2e6ef8f171c8a0e6405f67f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20S=C5=82omnicki?= Date: Mon, 5 Aug 2024 15:06:38 +0200 Subject: [PATCH 015/241] Use local IPFS gateway for test envs --- .github/workflows/ci-run.yml | 1 - .github/workflows/deploy-pr.yml | 1 + .github/workflows/e2e-run.yml | 1 + .github/workflows/tpl-deploy-app.yml | 5 +++++ ci/argocd/templates/octant-application.yaml | 2 +- 5 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-run.yml b/.github/workflows/ci-run.yml index 819676455e..feccc6ff39 100644 --- a/.github/workflows/ci-run.yml +++ b/.github/workflows/ci-run.yml @@ -10,7 +10,6 @@ env: GITLAB_PAT_OCTANT_K8S_DEVOPS_REPOSITORY_WRITE: "${{ secrets.GITLAB_PAT_OCTANT_K8S_DEVOPS_REPOSITORY_WRITE }}" jobs: - docker: name: Docker needs: diff --git a/.github/workflows/deploy-pr.yml b/.github/workflows/deploy-pr.yml index a8703199ff..3c27a7d756 100644 --- a/.github/workflows/deploy-pr.yml +++ b/.github/workflows/deploy-pr.yml @@ -41,6 +41,7 @@ jobs: anvil-block-time: 5 decision-window: 1800 epoch-duration: 3600 + ipfs-gateways: 'https://ipfs.octant.wildland.dev/ipfs/' secrets: inherit run: diff --git a/.github/workflows/e2e-run.yml b/.github/workflows/e2e-run.yml index e0b1321f7b..ac979b838d 100644 --- a/.github/workflows/e2e-run.yml +++ b/.github/workflows/e2e-run.yml @@ -72,6 +72,7 @@ jobs: glm-claim-enabled: true vault-confirm-withdrawals-enabled: true anvil-block-time: 5 + ipfs-gateways: 'https://ipfs.octant.wildland.dev/ipfs/' secrets: inherit run-e2e-tests: diff --git a/.github/workflows/tpl-deploy-app.yml b/.github/workflows/tpl-deploy-app.yml index b009f240db..5ff8349dac 100644 --- a/.github/workflows/tpl-deploy-app.yml +++ b/.github/workflows/tpl-deploy-app.yml @@ -118,6 +118,10 @@ on: required: false default: true type: boolean + ipfs-gateways: + required: false + default: 'https://turquoise-accused-gayal-88.mypinata.cloud/ipfs/,https://octant.infura-ipfs.io/ipfs/' + type: string env: ENV_TYPE: ${{ inputs.env-type }} @@ -160,6 +164,7 @@ env: TESTNET_RPC_URL: "${{ secrets.TESTNET_RPC_URL }}" ETHERSCAN_API_KEY: "${{ secrets.ETHERSCAN_API_KEY }}" VITE_ALCHEMY_ID: "${{ secrets.VITE_ALCHEMY_ID }}" + IPFS_GATEWAYS: "${{ inputs.ipfs-gateways }}" MULTIDEPLOYER_ENABLED: ${{ inputs.multideployer-enabled }} SUBGRAPH_DEPLOY: ${{ inputs.subgraph-deploy }} GRAPH_HEALTCHECKER_ENABLED: ${{ inputs.graph-healtchecker-enabled }} diff --git a/ci/argocd/templates/octant-application.yaml b/ci/argocd/templates/octant-application.yaml index dfd7d9ffd2..b06817490f 100644 --- a/ci/argocd/templates/octant-application.yaml +++ b/ci/argocd/templates/octant-application.yaml @@ -34,7 +34,7 @@ spec: - name: 'webClient.hideCurrentProjectsOutsideAW' value: 'false' - name: 'webClient.ipfsGateways' - value: 'https://ipfs.octant.wildland.dev/ipfs/' + value: '$IPFS_GATEWAYS' ## Graph Node - name: graphNode.graph.env.NETWORK value: '$NETWORK_NAME' From 43ac1418ad2d38d0f3b50247fc5fac97ef53c0af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Garbaci=C5=84ski?= <57113816+kgarbacinski@users.noreply.github.com> Date: Wed, 7 Aug 2024 14:22:43 +0200 Subject: [PATCH 016/241] OCT-1730: Improvements for the epoch verifier (#368) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description ## Definition of Done 1. [ ] Acceptance criteria are met. 2. [ ] PR is manually tested before the merge by developer(s). - [ ] Happy path is manually checked. 3. [ ] PR is manually tested by QA when their assistance is required (1). - [ ] Octant Areas & Test Cases are checked for impact and updated if required (2). 4. [ ] Unit tests are added unless there is a reason to omit them. 5. [ ] Automated tests are added when required. 6. [ ] The code is merged. 7. [ ] Tech documentation is added / updated, reviewed and approved (including mandatory approval by a code owner, should such exist for changed files). - [ ] BE: Swagger documentation is updated. 8. [ ] When required by QA: - [ ] Deployed to the relevant environment. - [ ] Passed system tests. --- (1) Developer(s) in coordination with QA decide whether it's required. For small tickets introducing small changes QA assistance is most probably not required. (2) [Octant Areas & Test Cases](https://docs.google.com/spreadsheets/d/1cRe6dxuKJV3a4ZskAwWEPvrFkQm6rEfyUCYwLTYw_Cc). --------- Co-authored-by: Andrzej Ziółek --- epoch-verifier/README.md | 8 ++- epoch-verifier/src/data/context.ts | 37 ++++++++++---- epoch-verifier/src/data/fetcher.ts | 15 ++++-- epoch-verifier/src/data/models.ts | 49 ++++++++++++++++++- epoch-verifier/src/verifications/donations.ts | 26 +++++++--- epoch-verifier/src/verifier.ts | 37 +++++++++----- 6 files changed, 137 insertions(+), 35 deletions(-) diff --git a/epoch-verifier/README.md b/epoch-verifier/README.md index 93eed44a41..9f8e7d7427 100644 --- a/epoch-verifier/README.md +++ b/epoch-verifier/README.md @@ -18,8 +18,14 @@ Check usage with: yarn verify:help ``` +If you want to use the script before the end of the AW, you can use the `--simulated` option. This will use the current state of the BE and simulate the end of the AW. -Script has already defined several, most important deployments (`--deployment` option), but one can connect to any other octant backend using `--url` option. +e.g +``` +yarn verify:mainnet --simulated +``` + +Script has already defined several, most important deployments (`--deployment` option), but one can connect to any other octant backend using `--url` option. diff --git a/epoch-verifier/src/data/context.ts b/epoch-verifier/src/data/context.ts index c585c9954d..7cc11d9ccf 100644 --- a/epoch-verifier/src/data/context.ts +++ b/epoch-verifier/src/data/context.ts @@ -6,15 +6,17 @@ import { Reward, UserBudget, UserDonation, - EpochUqs + EpochUqs, + SimulatedReward } from "./models" export interface Context { - allocations: Allocation[] - budgets: Map - epochInfo: EpochInfo - rewards: Reward[] - uqs: Map + allocations: Allocation[]; + budgets: Map; + epochInfo: EpochInfo; + rewards: Reward[]; + uqs: Map; + isSimulated: boolean; } function transformToAllocations(allocationRecords: AllocationRecord[]): Allocation[] { @@ -29,7 +31,7 @@ function transformToAllocations(allocationRecords: AllocationRecord[]): Allocati return Array.from(allocations.values()) } -export function buildContext(userBudgets: UserBudget[], allocations: AllocationRecord[], rewards: Reward[], epochInfo: EpochInfo, epochUqs: EpochUqs[]): Context { +export function buildContext(userBudgets: UserBudget[], allocations: AllocationRecord[], rewards: Reward[], epochInfo: EpochInfo, epochUqs: EpochUqs[], isSimulated: boolean): Context { const positiveUserBudgets = userBudgets.filter(positiveUserBudget => positiveUserBudget.amount !== BigInt(0)); @@ -38,7 +40,8 @@ export function buildContext(userBudgets: UserBudget[], allocations: AllocationR budgets: new Map(positiveUserBudgets.map(value => [value.user, value.amount] as const)), epochInfo, rewards, - epochUqs: new Map(epochUqs.map(value => [value.userAddress, value.uniquenessQuotient] as const)) + epochUqs: new Map(epochUqs.map(value => [value.userAddress, value.uniquenessQuotient] as const)), + isSimulated } } @@ -81,9 +84,23 @@ export function individualDonationsAggregatedByProjectsWithUQs(context: Context) return individualDonations; } -export function rewardsByProject(context: Context): Map { - return new Map(context.rewards +function _getSimulatedRewardsByProject(rewards: SimulatedReward[]): Map { + return new Map(rewards + .filter(r => r.matched !== BigInt(0)) + .map((r) => [r.address, r] as const) + ); +} + +function _getRewardsByProject(rewards: Reward[]): Map { + return new Map(rewards .filter(r => r.matched !== BigInt(0)) .map((r) => [r.project, r] as const) ); } + +export function rewardsByProject(context: Context): Map { + if (context.isSimulated) { + return _getSimulatedRewardsByProject(context.rewards.projectsRewards); + } + return _getRewardsByProject(context.rewards); +} diff --git a/epoch-verifier/src/data/fetcher.ts b/epoch-verifier/src/data/fetcher.ts index b637bde54b..8273e5398d 100644 --- a/epoch-verifier/src/data/fetcher.ts +++ b/epoch-verifier/src/data/fetcher.ts @@ -12,7 +12,8 @@ import { UserBudget, AllocationRecord, ApiRewardsBudgets, ApiAllocations, ApiRewards, - ApiEpochUqs, EpochUqsImpl + ApiEpochUqs, EpochUqsImpl, + FinalizedSimulationImpl } from "./models"; const REQUEST_TIMEOUT = 150_000; @@ -60,10 +61,6 @@ export class HttpFetcher { return this._get_array(`/allocations/epoch/${epoch}?includeZeroAllocations=true`, "users' allocations", AllocationImpl, (data: ApiAllocations) => data.allocations) } - async apiGetRewards(epoch: number): Promise { - return this._get_array(`/rewards/projects/epoch/${epoch}`, "projects rewards", RewardImpl, (data: ApiRewards) => data.rewards) - } - async apiGetEpochInfo(epoch: number): Promise { return this._get(`/epochs/info/${epoch}`, "epoch info", EpochInfoImpl) } @@ -71,4 +68,12 @@ export class HttpFetcher { async apiGetEpochUqs(epoch: number): Promise { return this._get_array(`/user/uq/${epoch}/all`, "epoch uqs", EpochUqsImpl, (data: ApiEpochUqs) => data.uqsInfo); } + + async apiGetRewards(epoch: number): Promise { + return this._get_array(`/rewards/projects/epoch/${epoch}`, "projects rewards", RewardImpl, (data: ApiRewards) => data.rewards) + } + + async apiGetFinalizedSimulated() { + return this._get('/snapshots/finalized/simulate', 'finalized simulated snapshot', FinalizedSimulationImpl); + } } diff --git a/epoch-verifier/src/data/models.ts b/epoch-verifier/src/data/models.ts index c0da734b3c..99abb31539 100644 --- a/epoch-verifier/src/data/models.ts +++ b/epoch-verifier/src/data/models.ts @@ -58,12 +58,20 @@ export interface Allocation { user: Address; } -export interface Reward { - allocated: bigint; +interface IReward { matched: bigint; +} + +export interface Reward extends IReward { + allocated: bigint; project: Address; } +export interface SimulatedReward extends IReward { + amount: bigint; // allocated + matched + address: Address; +} + export interface EpochInfo { communityFund: bigint; individualRewards: bigint; @@ -83,6 +91,12 @@ export interface EpochUqs { uniquenessQuotient: number; } +export interface FinalizedSimulation { + patronsRewards: bigint; + matchedRewards: bigint; + projectRewards: SimulatedReward[]; +} + export class UserBudgetImpl implements Deserializable { user: Address; @@ -128,6 +142,20 @@ export class RewardImpl implements Deserializable { } } +export class SimulatedRewardImpl implements Deserializable { + amount: bigint; + matched: bigint; + address: Address; + + from(input: any) { + this.address = input.address; + this.amount = BigInt(input.amount); + this.matched = BigInt(input.matched); + + return this + } +} + export class EpochInfoImpl implements Deserializable { individualRewards: bigint; @@ -180,3 +208,20 @@ export class EpochUqsImpl implements Deserializable{ } } +export class FinalizedSimulationImpl implements Deserializable{ + patronsRewards: bigint; + matchedRewards: bigint; + projectsRewards: SimulatedReward[]; + + from(input: FinalizedSimulation) { + this.patronsRewards = BigInt(input.patronsRewards); + this.matchedRewards = BigInt(input.matchedRewards); + this.projectsRewards = input.projectsRewards.map((reward: SimulatedReward) => { + const simulatedReward = new SimulatedRewardImpl(); + return simulatedReward.from(reward); + }); + + return this; + } +} + diff --git a/epoch-verifier/src/verifications/donations.ts b/epoch-verifier/src/verifications/donations.ts index c1bafeb5c8..6e6ce0e0f5 100644 --- a/epoch-verifier/src/verifications/donations.ts +++ b/epoch-verifier/src/verifications/donations.ts @@ -13,8 +13,7 @@ function _groupAllocationsByProjects(aggregatedDonations: Map { const sumOfSqrt = values.reduce((sum, value) => sum + sqrt(multiplyFloatByBigInt(value.uq, value.amount)), BigInt(0)); - const resultValue = sumOfSqrt ** BigInt(2); - + let resultValue = sumOfSqrt ** BigInt(2); totalPlainQF += resultValue; result.set(project, resultValue); @@ -74,22 +73,29 @@ function _getUserAllocationsForProjects(context: Context): Map export function verifyUserDonationsVsRewards(context: Context): VerificationResult { const projectsAllocations = Array.from(_getUserAllocationsForProjects(context).entries()); const rewards = rewardsByProject(context); + + if (context.isSimulated) { + return assertAll(projectsAllocations, ([proposal, allocated]) => assertEq(allocated, rewards.get(proposal)!.amount - rewards.get(proposal)!.matched, BigInt(100), true)); + } + return assertAll(projectsAllocations, ([proposal, allocated]) => assertEq(allocated, rewards.get(proposal)!.allocated, BigInt(100), true)); - return assertEq(10, 10, BigInt(100), true); } export function verifyRewardsVsUserDonations(context: Context): VerificationResult { const projectsAllocations = _getUserAllocationsForProjects(context); const rewards = Array.from(rewardsByProject(context).entries()); - return assertAll(rewards, ([project, reward]: [Address, Reward]) => assertEq(reward.allocated, projectsAllocations.get(project)!, BigInt(100), true)); - return assertEq(10, 10, BigInt(100), true); + if (context.isSimulated) { + return assertAll(rewards, ([project, reward]: [Address, Reward]) => assertEq(reward.amount - reward.matched, projectsAllocations.get(project)!, BigInt(100), true)); + } + + return assertAll(rewards, ([project, reward]: [Address, Reward]) => assertEq(reward.allocated, projectsAllocations.get(project)!, BigInt(100), true)); } export function verifyMatchedFunds(context: Context): VerificationResult { const rewards = rewardsByProject(context) const totalMatchedFunding = context.epochInfo.matchedRewards; - const aggregatedDonations = individualDonationsAggregatedByProjectsWithUQs(context) + const aggregatedDonations = individualDonationsAggregatedByProjectsWithUQs(context); const [aggregatedCappedMatchedFunding, leftover] = _computeMatchingFundQFAndCapAndUQ(aggregatedDonations, totalMatchedFunding); return assertAll(aggregatedCappedMatchedFunding, ([project, matched]) => { @@ -116,6 +122,14 @@ export function verifyTotalWithdrawals(context: Context): VerificationResult { .map(([user, donationsSum]) => context.budgets.get(user)! - donationsSum) .reduce((acc, user_claimed) => acc + user_claimed, BigInt(0)) + if (context.isSimulated) { + const rewards = context.rewards.projectsRewards + .filter((reward) => reward.matched !== BigInt(0)) + .reduce((acc, reward) => acc + reward.amount, BigInt(0)) + + return assertEq(claimed + rewards, context.epochInfo.totalWithdrawals) + } + const rewards = context.rewards .filter((reward) => reward.matched !== BigInt(0)) .reduce((acc, reward) => acc + reward.allocated + reward.matched, BigInt(0)) diff --git a/epoch-verifier/src/verifier.ts b/epoch-verifier/src/verifier.ts index f47f9fe3b0..f79be5975b 100644 --- a/epoch-verifier/src/verifier.ts +++ b/epoch-verifier/src/verifier.ts @@ -8,9 +8,10 @@ import { registerVerifications } from "./verifications"; interface Options { - deployment?: string - epoch: number - url?: string + deployment?: string; + epoch: number; + url?: string; + simulated?: boolean; } const DEPLOYMENTS: { [key: string]: string } = { @@ -39,20 +40,33 @@ function getDeploymentUrl(options: Options): string { } async function run(epoch: string, opts: any) { - const options: Options = { epoch: parseInt(epoch, 10), ...opts } + const options: Options = { epoch: parseInt(epoch, 10), simulated: opts.simulated, ...opts } const baseUrl = getDeploymentUrl(options) console.log(`Using url: ${baseUrl}`) - console.log(`Running verification scripts for epoch: ${options.epoch}`) + + if (options.simulated) { + console.log(`Running verification scripts for epoch: ${options.epoch} in simulated mode.`) + } + else { + console.log(`Running verification scripts for epoch: ${options.epoch}.`) + } const fetcher = new HttpFetcher(baseUrl) - const results = await Promise.all([ + const fetchPromises = [ fetcher.apiGetUserBudgets(options.epoch), fetcher.apiGetAllocations(options.epoch), - fetcher.apiGetRewards(options.epoch), fetcher.apiGetEpochInfo(options.epoch), fetcher.apiGetEpochUqs(options.epoch) - ]) + ]; + + if (options.simulated) { + fetchPromises.push(fetcher.apiGetFinalizedSimulated()); + } else { + fetchPromises.push(fetcher.apiGetRewards(options.epoch)); + } + + const results = await Promise.all(fetchPromises); if (results.some(isNull)) { process.exit(1) @@ -61,11 +75,11 @@ async function run(epoch: string, opts: any) { const [ userBudgets, allocations, - rewards, epochInfo, - epochUqs + epochUqs, + rewards ] = results; - const context = buildContext(userBudgets!, allocations!, rewards!, epochInfo!, epochUqs!) + const context = buildContext(userBudgets!, allocations!, rewards!, epochInfo!, epochUqs!, options.simulated); const runner = new Runner() registerVerifications(runner) @@ -81,6 +95,7 @@ program .description("Epoch verifier script.") .addOption(new Option("--deployment ", "specify deployment to connect to").choices(Object.keys(DEPLOYMENTS))) .option("--url ", "custom deployment url. Do not use with --deployment option") + .option("--simulated", "run the script in simulated mode") .argument("", "Epoch number for which the verification should be done.") .action(run) .parse(process.argv); From 329d8d19e9c4d7ad63a82bb68e7246530c8e2890 Mon Sep 17 00:00:00 2001 From: Mateusz Stolecki Date: Fri, 31 May 2024 10:54:37 +0200 Subject: [PATCH 017/241] basic api tests for history endpoint Signed-off-by: Mateusz Stolecki Signed-off-by: Mateusz Stolecki --- backend/tests/api-e2e/test_api_history.py | 50 +++++++++++++++++++++++ backend/tests/conftest.py | 4 ++ 2 files changed, 54 insertions(+) create mode 100644 backend/tests/api-e2e/test_api_history.py diff --git a/backend/tests/api-e2e/test_api_history.py b/backend/tests/api-e2e/test_api_history.py new file mode 100644 index 0000000000..000180a89d --- /dev/null +++ b/backend/tests/api-e2e/test_api_history.py @@ -0,0 +1,50 @@ +import pytest + +from tests.conftest import Client, UserAccount +from app.legacy.core.projects import get_projects_addresses +from tests.helpers.constants import STARTING_EPOCH +from flask import current_app as app + + +@pytest.mark.api +def test_history_basics( + client: Client, + deployer: UserAccount, + ua_alice: UserAccount, +): + # Check user history before allocation + user_history, status_code = client.get_user_history(ua_alice.address) + assert len(user_history["history"]) == 0, "User history should be empty" + assert status_code == 200 + + # Get alice proposals + alice_proposals = get_projects_addresses(1)[:3] + + # lock GLM for one account + ua_alice.lock(10000) + + # forward time to the beginning of the epoch 2 + client.move_to_next_epoch(STARTING_EPOCH + 1) + + # wait for indexer to catch up + epoch_no = client.wait_for_sync(STARTING_EPOCH + 1) + app.logger.debug(f"indexed epoch: {epoch_no}") + + # make a snapshot + res = client.pending_snapshot() + assert res["epoch"] > STARTING_EPOCH - 1 + + allocation_response_code = ua_alice.allocate(1000, alice_proposals) + assert ( + allocation_response_code == 201 + ), "Allocation status code is different than 201" + + # Check user history after allocation + user_history, status_code = client.get_user_history(ua_alice.address) + assert ( + user_history["history"][0]["type"] == "allocation" + ), "Type of history record should be 'allocation'" + assert ( + len(user_history["history"][0]["eventData"]["allocations"]) == 3 + ), "Number of allocations should be 3" + assert status_code == 200 diff --git a/backend/tests/conftest.py b/backend/tests/conftest.py index 154f8df309..236b003bd2 100644 --- a/backend/tests/conftest.py +++ b/backend/tests/conftest.py @@ -884,6 +884,10 @@ def accept_tos(self, user_address, signature): ) return json.loads(rv.text), rv.status_code + def get_user_history(self, user_address) -> tuple[dict, int]: + rv = self._flask_client.get(f"/history/{user_address}") + return json.loads(rv.text), rv.status_code + def get_antisybil_score(self, user_address: str) -> (any, int): rv = self._flask_client.get(f"/user/{user_address}/antisybil-status") return json.loads(rv.text), rv.status_code From 0c207baa114e647a6b1afd2f717b4eb7dc6a1736 Mon Sep 17 00:00:00 2001 From: Mateusz Stolecki Date: Thu, 1 Aug 2024 17:20:22 +0200 Subject: [PATCH 018/241] code review change --- backend/tests/api-e2e/test_api_history.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/tests/api-e2e/test_api_history.py b/backend/tests/api-e2e/test_api_history.py index 000180a89d..f5e719b2d2 100644 --- a/backend/tests/api-e2e/test_api_history.py +++ b/backend/tests/api-e2e/test_api_history.py @@ -32,7 +32,7 @@ def test_history_basics( # make a snapshot res = client.pending_snapshot() - assert res["epoch"] > STARTING_EPOCH - 1 + assert res["epoch"] == STARTING_EPOCH allocation_response_code = ua_alice.allocate(1000, alice_proposals) assert ( From e77fb43ade367980ef2bb9b05306c2f015e3993f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Kluczek?= Date: Thu, 8 Aug 2024 10:54:00 +0200 Subject: [PATCH 019/241] Reapply "OCT-1842: Reduce sentry events flood" This reverts commit 9fcf0a4dd2cd7d0899958c8eeafc50e4d63889dc. --- backend/app/infrastructure/apscheduler.py | 6 +++++- backend/startup.py | 23 +++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/backend/app/infrastructure/apscheduler.py b/backend/app/infrastructure/apscheduler.py index 35430ba1f0..223d97f7ca 100644 --- a/backend/app/infrastructure/apscheduler.py +++ b/backend/app/infrastructure/apscheduler.py @@ -1,5 +1,6 @@ from flask import current_app as app +from app import exceptions from app.legacy.core import glm from app.legacy.core import vault from app.extensions import scheduler @@ -16,7 +17,10 @@ def vault_confirm_withdrawals(): with scheduler.app.app_context(): if app.config["VAULT_CONFIRM_WITHDRAWALS_ENABLED"]: app.logger.debug("Confirming withdrawals in Vault contract...") - vault.confirm_withdrawals() + try: + vault.confirm_withdrawals() + except exceptions.MissingSnapshot: + app.logger.warning("No snapshot found") @scheduler.task( diff --git a/backend/startup.py b/backend/startup.py index f100cc84d9..861c38017a 100644 --- a/backend/startup.py +++ b/backend/startup.py @@ -20,12 +20,35 @@ if os.getenv("SENTRY_DSN"): import sentry_sdk + def sentry_before_send(event, hint): + exceptions = event.get("exception", []) + if not exceptions: + return event + + exc = exceptions[-1] + mechanism = exc.get("mechanism", {}) + + if mechanism.get("handled"): + return None + + return event + exceptions = event["exception"] + if exceptions: + exc = exceptions[-1] + mechanism = exc.get("mechanism") + if mechanism: + if mechanism.get("handled"): + return None + + return event + print("[+] Starting sentry") sentry_sdk.init( traces_sample_rate=1.0, profiles_sample_rate=1.0, enable_tracing=True, + before_send=sentry_before_send, ) from app import create_app # noqa From 2ee40eeda6874391322b7d0d48c5cc31cf24e5c8 Mon Sep 17 00:00:00 2001 From: Housekeeper Bot Date: Fri, 9 Aug 2024 06:58:20 +0000 Subject: [PATCH 020/241] [CI/CD] Update uat.env contracts --- ci/argocd/contracts/uat.env | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/ci/argocd/contracts/uat.env b/ci/argocd/contracts/uat.env index e973fb026f..cf8cd77b16 100644 --- a/ci/argocd/contracts/uat.env +++ b/ci/argocd/contracts/uat.env @@ -1,8 +1,8 @@ -BLOCK_NUMBER=6372841 +BLOCK_NUMBER=6465384 GLM_CONTRACT_ADDRESS=0x71432DD1ae7DB41706ee6a22148446087BdD0906 -AUTH_CONTRACT_ADDRESS=0xC0Dbb4117f48199C41784ACDdF534613435A633a -DEPOSITS_CONTRACT_ADDRESS=0x6462cC5dE29E1dd238f489a7bFfbb1C0E2A7543B -EPOCHS_CONTRACT_ADDRESS=0xD7c16938d22f111C7725a9f02abdbC282f7a4701 -PROPOSALS_CONTRACT_ADDRESS=0x95ECcB9f1b036718fCb6d35b28c0D7c0CB69b768 -WITHDRAWALS_TARGET_CONTRACT_ADDRESS=0x88a89528b7C1FaadCB4A83765206cfbDFafCa077 -VAULT_CONTRACT_ADDRESS=0x5833e8e7d4d90d3B1b872186DEB62faE97DA6634 +AUTH_CONTRACT_ADDRESS=0x8650350D56Bd0D70e6Bb75bb03393Aa9aA31df76 +DEPOSITS_CONTRACT_ADDRESS=0xeffD3C3dF8255A6903dF485c2C7420ECa2499b7b +EPOCHS_CONTRACT_ADDRESS=0xa8139B805Cf25BF8CfFb57B543a4a9bE82Ac03D0 +PROPOSALS_CONTRACT_ADDRESS=0x5C984D99aeEe996d3193CBc1793c3334a53cC86a +WITHDRAWALS_TARGET_CONTRACT_ADDRESS=0x3CAAf7E45F7E7cF708D1161ffa551A87DaC50d41 +VAULT_CONTRACT_ADDRESS=0xfE6cdc5656aF0A961Db9e9525fDB609344D42156 From 6ba54d3dbae0062b3309bdc2d0bef5848fe204f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Garbaci=C5=84ski?= <57113816+kgarbacinski@users.noreply.github.com> Date: Fri, 9 Aug 2024 12:43:01 +0200 Subject: [PATCH 021/241] FIX-OCT-1839: Set created_at field (#376) ## Description ## Definition of Done 1. [ ] Acceptance criteria are met. 2. [ ] PR is manually tested before the merge by developer(s). - [ ] Happy path is manually checked. 3. [ ] PR is manually tested by QA when their assistance is required (1). - [ ] Octant Areas & Test Cases are checked for impact and updated if required (2). 4. [ ] Unit tests are added unless there is a reason to omit them. 5. [ ] Automated tests are added when required. 6. [ ] The code is merged. 7. [ ] Tech documentation is added / updated, reviewed and approved (including mandatory approval by a code owner, should such exist for changed files). - [ ] BE: Swagger documentation is updated. 8. [ ] When required by QA: - [ ] Deployed to the relevant environment. - [ ] Passed system tests. --- (1) Developer(s) in coordination with QA decide whether it's required. For small tickets introducing small changes QA assistance is most probably not required. (2) [Octant Areas & Test Cases](https://docs.google.com/spreadsheets/d/1cRe6dxuKJV3a4ZskAwWEPvrFkQm6rEfyUCYwLTYw_Cc). --- .../8b425b454a86_fix_created_at_field.py | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 backend/migrations/versions/8b425b454a86_fix_created_at_field.py diff --git a/backend/migrations/versions/8b425b454a86_fix_created_at_field.py b/backend/migrations/versions/8b425b454a86_fix_created_at_field.py new file mode 100644 index 0000000000..0d8c01b0aa --- /dev/null +++ b/backend/migrations/versions/8b425b454a86_fix_created_at_field.py @@ -0,0 +1,28 @@ +"""Fix created_at field + +Revision ID: 8b425b454a86 +Revises: 999999999999 +Create Date: 2024-08-02 12:32:50.490759 + +""" +from alembic import op +import sqlalchemy as sa + +revision = "8b425b454a86" +down_revision = "999999999999" +branch_labels = None +depends_on = None + +HASH1 = "a1ee927c11efc35ffef40fa51547e0770df76aab9085da332311ac9d629fa518" +HASH2 = "f6b78725294faab4442f38aedb97ff7bc8fcaf9d73edf9845e1c57496e6d2913" +HASH3 = "93bf4d5bb695b96edd45c0d4eae59fe3f5ecc657f7137407288fd82834476a0b" + + +def upgrade(): + query = f"UPDATE score_delegation SET created_at = make_date(2024, 7, 17) WHERE hashed_addr IN ('{HASH1}', '{HASH2}', '{HASH3}');" + op.execute(query) + + +def downgrade(): + query = f"UPDATE score_delegation SET created_at = NULL WHERE hashed_addr IN ('{HASH1}', '{HASH2}', '{HASH3}');" + op.execute(query) From c5bae59af8525d442d5abb3400a5528a2132e384 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Miko=C5=82ajczyk?= Date: Tue, 13 Aug 2024 16:06:45 +0200 Subject: [PATCH 022/241] oct-1858: client v1.5 layout draft --- .../shared/Layout/Layout.module.scss | 10 +- .../src/components/shared/Layout/Layout.tsx | 237 ++++++++++-------- .../LayoutFooter/LayoutFooter.module.scss | 59 +++++ .../Layout/LayoutFooter/LayoutFooter.tsx | 60 +++++ .../LayoutNavbar/LayoutNavbar.module.scss | 28 ++- .../Layout/LayoutNavbar/LayoutNavbar.tsx | 18 +- .../LayoutTopBar/LayoutTopBar.module.scss | 125 +++++++++ .../Layout/LayoutTopBar/LayoutTopBar.tsx | 147 +++++++++++ .../components/ui/Button/Button.module.scss | 1 + .../navigationTabs/navigationTabs.ts | 11 +- client/src/constants/urls.ts | 5 +- client/src/locales/en/translation.json | 22 +- client/src/routes/RootRoutes/RootRoutes.tsx | 9 + client/src/routes/RootRoutes/routes.ts | 2 + client/src/store/layout/store.ts | 40 +++ client/src/store/layout/types.ts | 13 + client/src/styles/utils/_colors.scss | 3 +- client/src/styles/utils/_mediaQueries.scss | 8 +- client/src/styles/utils/_mixins.scss | 1 - client/src/svg/logo.ts | 6 + client/src/svg/navigation.ts | 8 +- client/src/utils/truncateEthAddress.ts | 6 +- client/src/views/HomeView/HomeView.tsx | 9 + 23 files changed, 684 insertions(+), 144 deletions(-) create mode 100644 client/src/components/shared/Layout/LayoutFooter/LayoutFooter.module.scss create mode 100644 client/src/components/shared/Layout/LayoutFooter/LayoutFooter.tsx create mode 100644 client/src/components/shared/Layout/LayoutTopBar/LayoutTopBar.module.scss create mode 100644 client/src/components/shared/Layout/LayoutTopBar/LayoutTopBar.tsx create mode 100644 client/src/store/layout/store.ts create mode 100644 client/src/store/layout/types.ts create mode 100644 client/src/views/HomeView/HomeView.tsx diff --git a/client/src/components/shared/Layout/Layout.module.scss b/client/src/components/shared/Layout/Layout.module.scss index 52e20f9a0d..1051f87d5e 100644 --- a/client/src/components/shared/Layout/Layout.module.scss +++ b/client/src/components/shared/Layout/Layout.module.scss @@ -9,12 +9,6 @@ $bodyPaddingTop: calc(1.6rem + $headerMarginTop + $headerHeight); min-height: 100%; } -.root, -.headerWrapper { - @include layoutFloatingElementWidth(); - margin: 0 auto; -} - .headerWrapper { position: fixed; top: 0; @@ -182,10 +176,10 @@ $bodyPaddingTop: calc(1.6rem + $headerMarginTop + $headerHeight); align-items: center; justify-content: flex-start; flex: 1; - padding: $bodyPaddingTop $layoutMarginHorizontal 12rem; + margin: 0 2.4rem; @media #{$desktop-up} { - padding-bottom: 15.2rem; + margin: 0 11.2rem; } &.isNavigationBottomSuffix { diff --git a/client/src/components/shared/Layout/Layout.tsx b/client/src/components/shared/Layout/Layout.tsx index f4dd3a3119..52d52f3508 100644 --- a/client/src/components/shared/Layout/Layout.tsx +++ b/client/src/components/shared/Layout/Layout.tsx @@ -1,98 +1,117 @@ import cx from 'classnames'; -import React, { FC, useState, Fragment, useMemo, useEffect } from 'react'; -import { Trans, useTranslation } from 'react-i18next'; -import { useLocation, useMatch, useNavigate } from 'react-router-dom'; -import { useAccount } from 'wagmi'; +import React, { + FC, + // useState, + Fragment, + useMemo, + // useEffect +} from 'react'; +// import { useTranslation } from 'react-i18next'; +import { + useLocation, + // useMatch, useNavigate +} from 'react-router-dom'; +// import { useAccount } from 'wagmi'; import LayoutNavbar from 'components/shared/Layout/LayoutNavbar'; import ModalLayoutConnectWallet from 'components/shared/Layout/ModalLayoutConnectWallet'; import ModalLayoutWallet from 'components/shared/Layout/ModalLayoutWallet'; -import Button from 'components/ui/Button'; import Loader from 'components/ui/Loader'; -import Svg from 'components/ui/Svg'; -import { ELEMENT_POSITION_FIXED_CLASSNAME } from 'constants/css'; import { LAYOUT_BODY_ID } from 'constants/domElementsIds'; import { adminNavigationTabs, navigationTabs as navigationTabsDefault, patronNavigationTabs, } from 'constants/navigationTabs/navigationTabs'; -import networkConfig from 'constants/networkConfig'; -import useEpochAndAllocationTimestamps from 'hooks/helpers/useEpochAndAllocationTimestamps'; -import useGetValuesToDisplay from 'hooks/helpers/useGetValuesToDisplay'; +// import useEpochAndAllocationTimestamps from 'hooks/helpers/useEpochAndAllocationTimestamps'; +// import useGetValuesToDisplay from 'hooks/helpers/useGetValuesToDisplay'; import useIsProjectAdminMode from 'hooks/helpers/useIsProjectAdminMode'; +import useMediaQuery from 'hooks/helpers/useMediaQuery'; import useCurrentEpoch from 'hooks/queries/useCurrentEpoch'; -import useIndividualReward from 'hooks/queries/useIndividualReward'; -import useIsDecisionWindowOpen from 'hooks/queries/useIsDecisionWindowOpen'; +// import useIndividualReward from 'hooks/queries/useIndividualReward'; +// import useIsDecisionWindowOpen from 'hooks/queries/useIsDecisionWindowOpen'; import useIsPatronMode from 'hooks/queries/useIsPatronMode'; -import useUserTOS from 'hooks/queries/useUserTOS'; +// import useUserTOS from 'hooks/queries/useUserTOS'; import { ROOT_ROUTES } from 'routes/RootRoutes/routes'; -import useSettingsStore from 'store/settings/store'; -import { octant } from 'svg/logo'; -import { chevronBottom } from 'svg/misc'; +import useLayoutStore from 'store/layout/store'; +// import useSettingsStore from 'store/settings/store'; import { chevronLeft } from 'svg/navigation'; -import getDifferenceInWeeks from 'utils/getDifferenceInWeeks'; +// import getDifferenceInWeeks from 'utils/getDifferenceInWeeks'; import getIsPreLaunch from 'utils/getIsPreLaunch'; -import getTimeDistance from 'utils/getTimeDistance'; -import truncateEthAddress from 'utils/truncateEthAddress'; +// import getTimeDistance from 'utils/getTimeDistance'; +// import truncateEthAddress from 'utils/truncateEthAddress'; import styles from './Layout.module.scss'; +import LayoutFooter from './LayoutFooter/LayoutFooter'; +import LayoutTopBar from './LayoutTopBar/LayoutTopBar'; import LayoutProps from './types'; const Layout: FC = ({ children, dataTest, navigationBottomSuffix, - isHeaderVisible = true, + // isHeaderVisible = true, isLoading, isNavigationVisible = true, classNameBody, - isAbsoluteHeaderPosition = false, - showHeaderBlur = true, + // isAbsoluteHeaderPosition = false, + // showHeaderBlur = true, }) => { + const { isDesktop } = useMediaQuery(); const { data: isPatronMode } = useIsPatronMode(); - const { i18n, t } = useTranslation('translation', { keyPrefix: 'layouts.main' }); - const [isModalConnectWalletOpen, setIsModalConnectWalletOpen] = useState(false); - const [isWalletModalOpen, setIsWalletModalOpen] = useState(false); - const { address, isConnected } = useAccount(); - const { data: individualReward } = useIndividualReward(); + // const { i18n, t } = useTranslation('translation', { keyPrefix: 'layout.main' }); + // const { address, isConnected } = useAccount(); + // const { data: individualReward } = useIndividualReward(); const { data: currentEpoch } = useCurrentEpoch(); - const { timeCurrentAllocationEnd, timeCurrentEpochEnd } = useEpochAndAllocationTimestamps(); - const { data: isDecisionWindowOpen } = useIsDecisionWindowOpen(); + // const { timeCurrentAllocationEnd, timeCurrentEpochEnd } = useEpochAndAllocationTimestamps(); + // const { data: isDecisionWindowOpen } = useIsDecisionWindowOpen(); const { pathname } = useLocation(); - const navigate = useNavigate(); - const { data: isUserTOSAccepted } = useUserTOS(); + // const navigate = useNavigate(); + // const { data: isUserTOSAccepted } = useUserTOS(); const isProjectAdminMode = useIsProjectAdminMode(); + // const { + // data: { isCryptoMainValueDisplay }, + // } = useSettingsStore(({ data }) => ({ + // data: { + // isCryptoMainValueDisplay: data.isCryptoMainValueDisplay, + // }, + // })); + const { - data: { isCryptoMainValueDisplay }, - } = useSettingsStore(({ data }) => ({ + setShowWalletModal, + setShowConnectWalletModal, + data: { showWalletModal, showConnectWalletModal }, + } = useLayoutStore(state => ({ data: { - isCryptoMainValueDisplay: data.isCryptoMainValueDisplay, + showConnectWalletModal: state.data.showConnectWalletModal, + showWalletModal: state.data.showWalletModal, }, + setShowConnectWalletModal: state.setShowConnectWalletModal, + setShowWalletModal: state.setShowWalletModal, })); const isPreLaunch = getIsPreLaunch(currentEpoch); - const isAllocationRoot = !!useMatch(ROOT_ROUTES.allocation.absolute); - const isUseMatchProject = !!useMatch(ROOT_ROUTES.projectWithAddress.absolute); - const isUseMatchProjectWithAddress = !!useMatch(ROOT_ROUTES.projectWithAddress.absolute); - const isProjectRoot = isUseMatchProject || isUseMatchProjectWithAddress; - const isProjectsRoot = !!useMatch(ROOT_ROUTES.projects.absolute); - const getValuesToDisplay = useGetValuesToDisplay(); + // const isAllocationRoot = !!useMatch(ROOT_ROUTES.allocation.absolute); + // const isUseMatchProject = !!useMatch(ROOT_ROUTES.projectWithAddress.absolute); + // const isUseMatchProjectWithAddress = !!useMatch(ROOT_ROUTES.projectWithAddress.absolute); + // const isProjectRoot = isUseMatchProject || isUseMatchProjectWithAddress; + // const isProjectsRoot = !!useMatch(ROOT_ROUTES.projects.absolute); + // const getValuesToDisplay = useGetValuesToDisplay(); - const showAllocationPeriod = isAllocationRoot || isProjectRoot || isProjectsRoot; + // const showAllocationPeriod = isAllocationRoot || isProjectRoot || isProjectsRoot; - const getCurrentPeriod = () => { - if (isDecisionWindowOpen && timeCurrentAllocationEnd) { - return getTimeDistance(Date.now(), new Date(timeCurrentAllocationEnd).getTime()); - } - if (!isDecisionWindowOpen && timeCurrentEpochEnd) { - return getTimeDistance(Date.now(), new Date(timeCurrentEpochEnd).getTime()); - } - return ''; - }; - const [currentPeriod, setCurrentPeriod] = useState(() => getCurrentPeriod()); + // const getCurrentPeriod = () => { + // if (isDecisionWindowOpen && timeCurrentAllocationEnd) { + // return getTimeDistance(Date.now(), new Date(timeCurrentAllocationEnd).getTime()); + // } + // if (!isDecisionWindowOpen && timeCurrentEpochEnd) { + // return getTimeDistance(Date.now(), new Date(timeCurrentEpochEnd).getTime()); + // } + // return ''; + // }; + // const [currentPeriod, setCurrentPeriod] = useState(() => getCurrentPeriod()); - const truncatedEthAddress = useMemo(() => address && truncateEthAddress(address), [address]); + // const truncatedEthAddress = useMemo(() => address && truncateEthAddress(address), [address]); const tabsWithIsActive = useMemo(() => { let tabs = navigationTabsDefault; @@ -117,67 +136,56 @@ const Layout: FC = ({ }); }, [isPatronMode, isProjectAdminMode, isPreLaunch, pathname]); - const isAllocationPeriodIsHighlighted = useMemo(() => { - if (isDecisionWindowOpen && timeCurrentAllocationEnd) { - return getDifferenceInWeeks(Date.now(), new Date(timeCurrentAllocationEnd).getTime()) < 1; - } - if (!isDecisionWindowOpen && timeCurrentEpochEnd) { - return getDifferenceInWeeks(Date.now(), new Date(timeCurrentEpochEnd).getTime()) < 1; - } - return false; - }, [isDecisionWindowOpen, timeCurrentAllocationEnd, timeCurrentEpochEnd]); + // const isAllocationPeriodIsHighlighted = useMemo(() => { + // if (isDecisionWindowOpen && timeCurrentAllocationEnd) { + // return getDifferenceInWeeks(Date.now(), new Date(timeCurrentAllocationEnd).getTime()) < 1; + // } + // if (!isDecisionWindowOpen && timeCurrentEpochEnd) { + // return getDifferenceInWeeks(Date.now(), new Date(timeCurrentEpochEnd).getTime()) < 1; + // } + // return false; + // }, [isDecisionWindowOpen, timeCurrentAllocationEnd, timeCurrentEpochEnd]); - const individualRewardText = useMemo(() => { - if (currentEpoch === 1 || individualReward === 0n || !isDecisionWindowOpen) { - return i18n.t('layouts.main.noRewardsYet'); - } - if (currentEpoch === undefined || individualReward === undefined) { - return i18n.t('layouts.main.loadingRewardBudget'); - } - return i18n.t('common.rewards', { - rewards: getValuesToDisplay({ - cryptoCurrency: 'ethereum', - showCryptoSuffix: true, - valueCrypto: individualReward, - }).primary, - }); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [individualReward, currentEpoch, isDecisionWindowOpen, isCryptoMainValueDisplay]); + // const individualRewardText = useMemo(() => { + // if (currentEpoch === 1 || individualReward === 0n || !isDecisionWindowOpen) { + // return i18n.t('layout.main.noRewardsYet'); + // } + // if (currentEpoch === undefined || individualReward === undefined) { + // return i18n.t('layout.main.loadingRewardBudget'); + // } + // return i18n.t('common.rewards', { + // rewards: getValuesToDisplay({ + // cryptoCurrency: 'ethereum', + // showCryptoSuffix: true, + // valueCrypto: individualReward, + // }).primary, + // }); + // // eslint-disable-next-line react-hooks/exhaustive-deps + // }, [individualReward, currentEpoch, isDecisionWindowOpen, isCryptoMainValueDisplay]); - const onLogoClick = () => { - if (pathname === ROOT_ROUTES.projects.absolute) { - window.scrollTo({ behavior: 'smooth', top: 0 }); - return; - } + // const onLogoClick = () => { + // if (pathname === ROOT_ROUTES.projects.absolute) { + // window.scrollTo({ behavior: 'smooth', top: 0 }); + // return; + // } - navigate(ROOT_ROUTES.projects.absolute); - }; + // navigate(ROOT_ROUTES.projects.absolute); + // }; - useEffect(() => { - const intervalId = setInterval(() => { - setCurrentPeriod(getCurrentPeriod()); - }, 1000); + // useEffect(() => { + // const intervalId = setInterval(() => { + // setCurrentPeriod(getCurrentPeriod()); + // }, 1000); - return () => clearInterval(intervalId); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isDecisionWindowOpen, timeCurrentAllocationEnd, timeCurrentEpochEnd]); + // return () => clearInterval(intervalId); + // // eslint-disable-next-line react-hooks/exhaustive-deps + // }, [isDecisionWindowOpen, timeCurrentAllocationEnd, timeCurrentEpochEnd]); return ( - setIsWalletModalOpen(false), - }} - /> - setIsModalConnectWalletOpen(false), - }} - />
- {isHeaderVisible && ( + + {/* {isHeaderVisible && ( {showHeaderBlur &&
}
= ({ ]} i18nKey={ isDecisionWindowOpen - ? 'layouts.main.allocationEndsIn' - : 'layouts.main.allocationStartsIn' + ? 'layout.main.allocationEndsIn' + : 'layout.main.allocationStartsIn' } values={{ currentPeriod }} /> @@ -276,7 +284,7 @@ const Layout: FC = ({
- )} + )} */}
= ({ > {isLoading ? : children}
- {isNavigationVisible && ( + {!isDesktop && isNavigationVisible && ( )} +
+ setShowWalletModal(false), + }} + /> + setShowConnectWalletModal(false), + }} + />
); }; diff --git a/client/src/components/shared/Layout/LayoutFooter/LayoutFooter.module.scss b/client/src/components/shared/Layout/LayoutFooter/LayoutFooter.module.scss new file mode 100644 index 0000000000..095df645f1 --- /dev/null +++ b/client/src/components/shared/Layout/LayoutFooter/LayoutFooter.module.scss @@ -0,0 +1,59 @@ +.root { + border-top: 0.1rem solid $color-octant-grey1; + display: flex; + align-items: center; + width: 100%; + padding: 4.8rem 3.2rem 15.2rem; + flex-direction: column-reverse; + + @media #{$tablet-up} { + flex-direction: row; + padding: 4.8rem 2.4rem 18.4rem; + } + + @media #{$desktop-up} { + padding: 4.8rem 11.2rem 5.6rem; + margin-bottom: 0; + } + + .wrapper { + display: flex; + text-align: start; + align-items: center; + margin-right: 2.4rem; + + .octantText { + font-size: $font-size-12; + min-width: 23.2rem; + margin-left: 1.6rem; + font-weight: $font-weight-semibold; + line-height: 2rem; + color: $color-octant-grey5; + } + } + + .links { + display: grid; + grid-template-columns: repeat(3, 11.2rem [col-start]); + grid-template-rows: 2.4rem 2.4rem; + grid-auto-flow: column; + margin-bottom: 3.2rem; + + @media #{$tablet-up} { + margin: 0; + } + + .link { + cursor: pointer; + display: flex; + font-size: $font-size-14; + font-weight: $font-weight-semibold; + color: $color-octant-grey5; + } + } + + .golemFoundationLink { + color: $color-octant-green; + text-decoration: underline; + } +} diff --git a/client/src/components/shared/Layout/LayoutFooter/LayoutFooter.tsx b/client/src/components/shared/Layout/LayoutFooter/LayoutFooter.tsx new file mode 100644 index 0000000000..599d8ace93 --- /dev/null +++ b/client/src/components/shared/Layout/LayoutFooter/LayoutFooter.tsx @@ -0,0 +1,60 @@ +import React, { memo, ReactElement } from 'react'; +import { Trans, useTranslation } from 'react-i18next'; + +import Svg from 'components/ui/Svg'; +import { + BLOG_POST, + BRAND_ASSETS_FIGMA_LINK, + DISCORD_LINK, + GOLEM_FOUNDATION_LINK, + OCTANT_BUILD_LINK, + OCTANT_DOCS, + TERMS_OF_USE, +} from 'constants/urls'; +import { octantSemiTransparent } from 'svg/logo'; + +import styles from './LayoutFooter.module.scss'; + +const LayoutFooter = (): ReactElement => { + const { t } = useTranslation('translation', { keyPrefix: 'layout.footer' }); + + const links = [ + { label: t('links.website'), link: OCTANT_BUILD_LINK }, + { label: t('links.discord'), link: DISCORD_LINK }, + { label: t('links.blog'), link: BLOG_POST }, + { label: t('links.docs'), link: OCTANT_DOCS }, + { label: t('links.brandAssets'), link: BRAND_ASSETS_FIGMA_LINK }, + { label: t('links.termsOfUse'), link: TERMS_OF_USE }, + ]; + + return ( +
+
+ +
+ , + ]} + i18nKey="layout.footer.octantText" + /> +
+
+
+ {links.map(({ link, label }) => ( + + {`→ ${label}`} + + ))} +
+
+ ); +}; + +export default memo(LayoutFooter); diff --git a/client/src/components/shared/Layout/LayoutNavbar/LayoutNavbar.module.scss b/client/src/components/shared/Layout/LayoutNavbar/LayoutNavbar.module.scss index 4c606cf73b..925a8a7e09 100644 --- a/client/src/components/shared/Layout/LayoutNavbar/LayoutNavbar.module.scss +++ b/client/src/components/shared/Layout/LayoutNavbar/LayoutNavbar.module.scss @@ -20,7 +20,7 @@ pointer-events: initial; width: 34.2rem; - @media #{$desktop-up} { + @media #{$tablet-up} { border-radius: $border-radius-40; width: 43.2rem; } @@ -29,7 +29,7 @@ padding: 1.6rem; border-bottom: 0.1rem solid $color-octant-grey3; - @media #{$desktop-up} { + @media #{$tablet-up} { padding: 2.4rem; } } @@ -45,7 +45,7 @@ justify-content: center; } - @media #{$desktop-up} { + @media #{$tablet-up} { margin: 1.2rem 2.4rem 2rem 2.4rem; } } @@ -59,15 +59,29 @@ flex: 0; font-size: $font-size-10; + &.isActive { + svg.octantLogo { + path:nth-child(2) { + fill: $color-octant-dark; + } + } + } + &:not(.isActive):hover { color: $color-octant-grey5; svg path { stroke: $color-octant-grey5; } + + svg.octantLogo { + path:nth-child(2) { + fill: $color-octant-grey5; + } + } } - @media #{$desktop-up} { + @media #{$tablet-up} { font-size: $font-size-12; &:not(:last-child) { @@ -96,7 +110,7 @@ border-radius: 50%; transform: translateZ(0); // On webkit solves the flickering of text during the scale. - @media #{$desktop-up} { + @media #{$tablet-up} { height: 2rem; width: 2rem; font-size: $font-size-10; @@ -126,14 +140,14 @@ rgba(0, 0, 0, 1) ); - @media #{$desktop-up} { + @media #{$tablet-up} { height: 15.2rem; } &.hasNavigationBottomSuffix { height: 20rem; - @media #{$desktop-up} { + @media #{$tablet-up} { height: 24.8rem; } } diff --git a/client/src/components/shared/Layout/LayoutNavbar/LayoutNavbar.tsx b/client/src/components/shared/Layout/LayoutNavbar/LayoutNavbar.tsx index 5c6f629610..01f741fc32 100644 --- a/client/src/components/shared/Layout/LayoutNavbar/LayoutNavbar.tsx +++ b/client/src/components/shared/Layout/LayoutNavbar/LayoutNavbar.tsx @@ -25,7 +25,7 @@ const LayoutNavbar: FC = ({ navigationBottomSuffix, tabs }) = })); const allocationsPrevRef = useRef(allocations); - const { isDesktop } = useMediaQuery(); + const { isTablet } = useMediaQuery(); const location = useLocation(); const [scope, animate] = useAnimate(); const isProjectAdminMode = useIsProjectAdminMode(); @@ -37,7 +37,7 @@ const LayoutNavbar: FC = ({ navigationBottomSuffix, tabs }) = return; } animate([ - [scope?.current, { scale: [isDesktop ? 1.4 : 1.5] }, { duration: 0.15, ease: 'easeOut' }], + [scope?.current, { scale: [isTablet ? 1.4 : 1.5] }, { duration: 0.15, ease: 'easeOut' }], [scope?.current, { scale: 1 }, { duration: 0.15, ease: 'easeOut' }], ]); allocationsPrevRef.current = allocations; @@ -64,7 +64,19 @@ const LayoutNavbar: FC = ({ navigationBottomSuffix, tabs }) = key={index} className={cx(styles.buttonNavigation, isActive && styles.isActive)} dataTest={`Navbar__Button--${label}`} - Icon={} + Icon={ + to === ROOT_ROUTES.settings.absolute ? ( + + ) : ( + + ) + } isActive={isActive} isDisabled={isDisabled || areTabsDisabled} label={label} diff --git a/client/src/components/shared/Layout/LayoutTopBar/LayoutTopBar.module.scss b/client/src/components/shared/Layout/LayoutTopBar/LayoutTopBar.module.scss new file mode 100644 index 0000000000..729410cd09 --- /dev/null +++ b/client/src/components/shared/Layout/LayoutTopBar/LayoutTopBar.module.scss @@ -0,0 +1,125 @@ +.root { + display: flex; + min-height: 8rem; + width: 100%; + align-items: center; + padding: 0 2.4rem; + position: relative; + border-bottom: 0.1rem solid $color-octant-grey1; + + @media #{$desktop-up} { + padding: 0 4rem; + } + + @media #{$desktop-up} { + padding: 0 11.2rem; + } + + .octantLogo { + cursor: pointer; + margin-right: 4rem; + + @media #{$tablet-up} { + margin-right: 13rem; + } + + @media #{$large-desktop-up} { + margin-right: 0; + } + } + + .links { + display: flex; + margin-left: 2.4rem; + + .link { + font-size: $font-size-14; + font-weight: $font-weight-bold; + color: $color-octant-grey2; + cursor: pointer; + + &:not(:last-child) { + margin-right: 2.4rem; + } + + &.isActive { + color: $color-octant-dark; + } + } + } + + .allocationInfo { + margin: 0 auto; + height: 4rem; + display: flex; + align-items: center; + font-size: $font-size-14; + font-weight: $font-weight-bold; + line-height: 1.4rem; + padding: 1rem 1.4rem; + background: $color-octant-green9; + color: $color-octant-green; + border-radius: $border-radius-16; + + .calendarIcon { + margin-right: 1.2rem; + } + } + + .buttonWallet { + cursor: pointer; + font-weight: $font-weight-bold; + font-size: $font-size-12; + padding: 0 0.2rem 0 1.2rem; + margin-left: 2.4rem; + justify-content: left; + min-width: 8.5rem; + + @media #{$tablet-up} { + font-size: $font-size-14; + padding-left: 1.5rem; + min-width: 14.6rem; + } + + .buttonWalletArrow { + margin: 1.1rem; + path { + stroke: $color-white; + } + } + + &.isConnectButton { + justify-content: center; + padding: 0; + } + } + + .settingsButton, + .allocateButton { + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + min-width: 4rem; + min-height: 4rem; + max-width: 4rem; + max-height: 4rem; + background-color: $color-white; + border-radius: $border-radius-16; + margin-left: 1.6rem; + } + + .allocateButtonIcon { + margin-left: 0.3rem; + + path { + stroke: $color-octant-grey2; + } + } + + .settingsButtonIcon { + path { + stroke: $color-octant-grey2; + } + } +} diff --git a/client/src/components/shared/Layout/LayoutTopBar/LayoutTopBar.tsx b/client/src/components/shared/Layout/LayoutTopBar/LayoutTopBar.tsx new file mode 100644 index 0000000000..dde3cbfaad --- /dev/null +++ b/client/src/components/shared/Layout/LayoutTopBar/LayoutTopBar.tsx @@ -0,0 +1,147 @@ +import cx from 'classnames'; +import React, { Fragment, ReactNode, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useLocation, useNavigate } from 'react-router-dom'; +import { useAccount } from 'wagmi'; + +import Button from 'components/ui/Button'; +import Svg from 'components/ui/Svg'; +import { + // adminNavigationTabs, + navigationTabs as navigationTabsDefault, + // patronNavigationTabs, +} from 'constants/navigationTabs/navigationTabs'; +import useMediaQuery from 'hooks/helpers/useMediaQuery'; +import useCurrentEpoch from 'hooks/queries/useCurrentEpoch'; +import useIsDecisionWindowOpen from 'hooks/queries/useIsDecisionWindowOpen'; +import { ROOT_ROUTES } from 'routes/RootRoutes/routes'; +import useLayoutStore from 'store/layout/store'; +import { octant } from 'svg/logo'; +import { chevronBottom } from 'svg/misc'; +import { allocate, settings } from 'svg/navigation'; +import getIsPreLaunch from 'utils/getIsPreLaunch'; +import truncateEthAddress from 'utils/truncateEthAddress'; + +import styles from './LayoutTopBar.module.scss'; + +const LayoutTopBar = (): ReactNode => { + const { t } = useTranslation('translation', { keyPrefix: 'layout.topBar' }); + const { isTablet, isLargeDesktop, isMobile } = useMediaQuery(); + const { isConnected, address } = useAccount(); + const { pathname } = useLocation(); + const navigate = useNavigate(); + const { data: isDecisionWindowOpen } = useIsDecisionWindowOpen(); + const { data: currentEpoch } = useCurrentEpoch(); + const isPreLaunch = getIsPreLaunch(currentEpoch); + const { setShowWalletModal, setShowConnectWalletModal } = useLayoutStore(state => ({ + setShowConnectWalletModal: state.setShowConnectWalletModal, + setShowWalletModal: state.setShowWalletModal, + })); + + const tabsWithIsActive = useMemo(() => { + const tabs = navigationTabsDefault.filter( + ({ to }) => to !== ROOT_ROUTES.allocation.absolute && to !== ROOT_ROUTES.settings.absolute, + ); + + // if (isPatronMode) { + // tabs = patronNavigationTabs; + // } + // if (isProjectAdminMode) { + // tabs = adminNavigationTabs; + // } + + return tabs.map(tab => { + const isProjectView = + pathname.includes(`${ROOT_ROUTES.project.absolute}/`) && + tab.to === ROOT_ROUTES.projects.absolute; + return { + ...tab, + // icon: isProjectView ? chevronLeft : tab.icon, + isActive: tab.isActive || pathname === tab.to || isProjectView, + isDisabled: isPreLaunch && tab.to !== ROOT_ROUTES.earn.absolute, + }; + }); + }, [ + // isPatronMode, isProjectAdminMode, + isPreLaunch, + pathname, + ]); + + const allocationInfoText = useMemo(() => { + const epoch = currentEpoch! - 1; + + if (isDecisionWindowOpen) { + return isMobile + ? t('epochAllocationWindowOpenShort', { epoch }) + : t('epochAllocationWindowOpen', { epoch }); + } + + return isMobile + ? t('epochAllocationWindowClosedShort', { epoch }) + : t('epochAllocationWindowClosed', { epoch }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isDecisionWindowOpen, currentEpoch, isMobile]); + + const buttonWalletText = useMemo(() => { + if (!isConnected) { + return !isMobile ? t('connectWallet') : t('connect'); + } + + return truncateEthAddress(address!, isMobile); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [address, isConnected, isMobile]); + + const onLogoClick = () => { + if (pathname === ROOT_ROUTES.projects.absolute) { + window.scrollTo({ behavior: 'smooth', top: 0 }); + return; + } + + navigate(ROOT_ROUTES.home.absolute); + }; + + return ( +
+ + {isLargeDesktop && ( +
+ {tabsWithIsActive.map(tab => ( +
navigate(tab.to)} + > + {tab.label} +
+ ))} +
+ )} +
+ {isTablet && } + {allocationInfoText} +
+ + {isLargeDesktop && ( + +
+ +
+
+ +
+
+ )} +
+ ); +}; + +export default LayoutTopBar; diff --git a/client/src/components/ui/Button/Button.module.scss b/client/src/components/ui/Button/Button.module.scss index ab64730e0d..eeaeec1bec 100644 --- a/client/src/components/ui/Button/Button.module.scss +++ b/client/src/components/ui/Button/Button.module.scss @@ -11,6 +11,7 @@ $paddingVertical: 1rem; border: 0; min-height: 4rem; font-size: $font-size-14; + cursor: pointer; &.isSmallFont { font-size: $font-size-12; diff --git a/client/src/constants/navigationTabs/navigationTabs.ts b/client/src/constants/navigationTabs/navigationTabs.ts index 293b2fb2bd..330ce14328 100644 --- a/client/src/constants/navigationTabs/navigationTabs.ts +++ b/client/src/constants/navigationTabs/navigationTabs.ts @@ -1,9 +1,15 @@ import { ROOT_ROUTES } from 'routes/RootRoutes/routes'; +import { octantInverted } from 'svg/logo'; import { allocate, earn, metrics, project, settings } from 'svg/navigation'; import { NavigationTab } from './types'; export const navigationTabs: NavigationTab[] = [ + { + icon: octantInverted, + label: 'Home', + to: ROOT_ROUTES.home.absolute, + }, { icon: project, label: 'Projects', @@ -14,11 +20,6 @@ export const navigationTabs: NavigationTab[] = [ label: 'Allocate', to: ROOT_ROUTES.allocation.absolute, }, - { - icon: earn, - label: 'Earn', - to: ROOT_ROUTES.earn.absolute, - }, { icon: metrics, label: 'Metrics', diff --git a/client/src/constants/urls.ts b/client/src/constants/urls.ts index 14fc5abca9..7e71ee1e0a 100644 --- a/client/src/constants/urls.ts +++ b/client/src/constants/urls.ts @@ -1,6 +1,9 @@ +export const BRAND_ASSETS_FIGMA_LINK = + 'https://www.figma.com/community/file/1295533951881708349/octant-brand-assets'; +export const GOLEM_FOUNDATION_LINK = 'https://golem.foundation/'; export const OCTANT_DOCS = 'https://docs.octant.app/'; export const DISCORD_LINK = 'https://discord.gg/octant'; -export const BLOG_POST = 'https://docs.octant.app/using-the-app.html'; +export const BLOG_POST = 'https://blog.octant.build/'; export const OCTANT_BUILD_LINK = 'https://octant.build/'; export const TWITTER_LINK = 'https://twitter.com/OctantApp'; export const TERMS_OF_USE = 'https://docs.octant.app/terms-of-use.html'; diff --git a/client/src/locales/en/translation.json b/client/src/locales/en/translation.json index 01da56577f..e43e2bb83a 100644 --- a/client/src/locales/en/translation.json +++ b/client/src/locales/en/translation.json @@ -299,7 +299,27 @@ } } }, - "layouts": { + "layout": { + "topBar": { + "epochAllocationWindowOpen": "Epoch {{epoch}} allocation window now open", + "epochAllocationWindowOpenShort": "E{{epoch}} allocation open", + "epochAllocationWindowClosed": "Epoch {{epoch}} allocation window is closed", + "epochAllocationWindowClosedShort": "E{{epoch}} allocation is closed", + "connectWallet": "Connect wallet", + "connect": "Connect" + }, + "footer": { + "octantText": "Octant is a <0>Golem Foundation project,
launched in 2023.", + "links": { + "website": "Website", + "discord": "Discord", + "blog": "Blog", + "docs": "Docs", + "brandAssets": "Brand assets", + "termsOfUse": "Terms of use" + + } + }, "main": { "buttonConnect": "Connect", "noRewardsYet": "No rewards yet", diff --git a/client/src/routes/RootRoutes/RootRoutes.tsx b/client/src/routes/RootRoutes/RootRoutes.tsx index 83689c19b9..2f1b939c8b 100644 --- a/client/src/routes/RootRoutes/RootRoutes.tsx +++ b/client/src/routes/RootRoutes/RootRoutes.tsx @@ -8,6 +8,7 @@ import useIsPatronMode from 'hooks/queries/useIsPatronMode'; import getIsPreLaunch from 'utils/getIsPreLaunch'; import AllocationView from 'views/AllocationView/AllocationView'; import EarnView from 'views/EarnView/EarnView'; +import HomeView from 'views/HomeView/HomeView'; import MetricsView from 'views/MetricsView/MetricsView'; import PlaygroundView from 'views/PlaygroundView/PlaygroundView'; import ProjectsView from 'views/ProjectsView/ProjectsView'; @@ -43,6 +44,14 @@ const RootRoutes: FC = props => { path={`${ROOT_ROUTES.allocation.relative}/*`} /> )} + + + + } + path={`${ROOT_ROUTES.home.relative}/*`} + /> diff --git a/client/src/routes/RootRoutes/routes.ts b/client/src/routes/RootRoutes/routes.ts index 17946794a6..4c6955281d 100644 --- a/client/src/routes/RootRoutes/routes.ts +++ b/client/src/routes/RootRoutes/routes.ts @@ -6,10 +6,12 @@ const PROJECT_PREFIX = 'project'; export const ROOT_ROUTES = { allocation: getPathObject(ROOT, 'allocation'), earn: getPathObject(ROOT, 'earn'), + home: getPathObject(ROOT, 'home'), metrics: getPathObject(ROOT, 'metrics'), playground: getPathObject(ROOT, 'playground'), project: getPathObject(ROOT, PROJECT_PREFIX), projectWithAddress: getPathObject(ROOT, `${PROJECT_PREFIX}/:epoch/:projectAddress`), projects: getPathObject(ROOT, 'projects'), + settings: getPathObject(ROOT, 'settings'), }; diff --git a/client/src/store/layout/store.ts b/client/src/store/layout/store.ts new file mode 100644 index 0000000000..afb743b6fa --- /dev/null +++ b/client/src/store/layout/store.ts @@ -0,0 +1,40 @@ +import { getStoreWithMeta } from 'store/utils/getStoreWithMeta'; + +import { LayoutData, LayoutMethods } from './types'; + +export const initialState: LayoutData = { + showAllocateDrawer: false, + showConnectWalletModal: false, + showSettingsDrawer: false, + showWalletModal: false, +}; + +export default getStoreWithMeta({ + getStoreMethods: set => ({ + // eslint-disable-next-line @typescript-eslint/naming-convention + setShowAllocateDrawer: payload => { + set(state => { + return { data: { ...state.data, showAllocateDrawer: payload } }; + }); + }, + // eslint-disable-next-line @typescript-eslint/naming-convention + setShowConnectWalletModal: payload => { + set(state => { + return { data: { ...state.data, showConnectWalletModal: payload } }; + }); + }, + // eslint-disable-next-line @typescript-eslint/naming-convention + setShowSettingsDrawer: payload => { + set(state => { + return { data: { ...state.data, showSettingsDrawer: payload } }; + }); + }, + // eslint-disable-next-line @typescript-eslint/naming-convention + setShowWalletModal: payload => { + set(state => { + return { data: { ...state.data, showWalletModal: payload } }; + }); + }, + }), + initialState, +}); diff --git a/client/src/store/layout/types.ts b/client/src/store/layout/types.ts new file mode 100644 index 0000000000..cbb5448731 --- /dev/null +++ b/client/src/store/layout/types.ts @@ -0,0 +1,13 @@ +export interface LayoutData { + showAllocateDrawer: boolean; + showConnectWalletModal: boolean; + showSettingsDrawer: boolean; + showWalletModal: boolean; +} + +export interface LayoutMethods { + setShowAllocateDrawer: (showAllocateDrawer: boolean) => void; + setShowConnectWalletModal: (showConnectWalletModal: boolean) => void; + setShowSettingsDrawer: (showSettingsDrawer: boolean) => void; + setShowWalletModal: (showWalletModal: boolean) => void; +} diff --git a/client/src/styles/utils/_colors.scss b/client/src/styles/utils/_colors.scss index d3c02a5f5d..05c04f943b 100644 --- a/client/src/styles/utils/_colors.scss +++ b/client/src/styles/utils/_colors.scss @@ -25,7 +25,8 @@ $color-octant-green4: #d1e7e3; // TODO OCT-1001 Remove this color. It will becom $color-octant-green5: #f1faf8; $color-octant-green6: #d3e9e4; $color-octant-green7: #c0d9d4; -$color-octant-green8: #D9F0EB; +$color-octant-green8: #d9f0eb; +$color-octant-green9: #e4efed; $color-octant-orange: #ff6157; $color-octant-orange2: #ff9601; $color-octant-orange3: #f6c54b; diff --git a/client/src/styles/utils/_mediaQueries.scss b/client/src/styles/utils/_mediaQueries.scss index f9a6e89fee..b9df9d4f64 100644 --- a/client/src/styles/utils/_mediaQueries.scss +++ b/client/src/styles/utils/_mediaQueries.scss @@ -1,10 +1,10 @@ // Media Query Ranges // ********************** -$phone-range: (0px, 471px); -$tablet-range: (472px, 755px); -$desktop-range: (756px); -$large-desktop-range: (1264px); +$phone-range: (0px, 743px); +$tablet-range: (744px, 1279px); +$desktop-range: (1280px); +$large-desktop-range: (1640px); // Range functions // We use these functions to define ranges for various things, like media queries. diff --git a/client/src/styles/utils/_mixins.scss b/client/src/styles/utils/_mixins.scss index e32f7c96e2..bb7033dad9 100644 --- a/client/src/styles/utils/_mixins.scss +++ b/client/src/styles/utils/_mixins.scss @@ -85,7 +85,6 @@ @mixin layoutFloatingElementWidth() { width: 100%; min-width: 39rem; - max-width: 70rem + $layoutMarginHorizontal * 2; } @mixin tipTileConnectWalletImage() { diff --git a/client/src/svg/logo.ts b/client/src/svg/logo.ts index 3a347c01d4..2d2484da56 100644 --- a/client/src/svg/logo.ts +++ b/client/src/svg/logo.ts @@ -6,6 +6,12 @@ export const octant: SvgImageConfig = { viewBox: '0 0 40 40', }; +export const octantInverted: SvgImageConfig = { + markup: + '', + viewBox: '0 0 40 40', +}; + export const octantSemiTransparent: SvgImageConfig = { markup: '', diff --git a/client/src/svg/navigation.ts b/client/src/svg/navigation.ts index 2c5e8eccfd..664ae46806 100644 --- a/client/src/svg/navigation.ts +++ b/client/src/svg/navigation.ts @@ -5,8 +5,8 @@ import styles from './style.module.scss'; export const allocate: SvgImageConfig = { className: styles.allocate, markup: - '', - viewBox: '0 0 32 32', + '', + viewBox: '0 0 20 20', }; export const metrics: SvgImageConfig = { @@ -23,8 +23,8 @@ export const project: SvgImageConfig = { export const settings: SvgImageConfig = { markup: - '', - viewBox: '0 0 32 32', + '', + viewBox: '0 0 22 22', }; export const earn: SvgImageConfig = { diff --git a/client/src/utils/truncateEthAddress.ts b/client/src/utils/truncateEthAddress.ts index 8cbd085161..0e288b60a3 100644 --- a/client/src/utils/truncateEthAddress.ts +++ b/client/src/utils/truncateEthAddress.ts @@ -1,6 +1,10 @@ -export default function truncateEthAddress(address: string): string { +export default function truncateEthAddress(address: string, isShortVersion?: boolean): string { const first = address.substring(0, 5); const last = address.substring(address.length - 4); + if (isShortVersion) { + return first; + } + return `${first}...${last}`; } diff --git a/client/src/views/HomeView/HomeView.tsx b/client/src/views/HomeView/HomeView.tsx new file mode 100644 index 0000000000..5786b32e67 --- /dev/null +++ b/client/src/views/HomeView/HomeView.tsx @@ -0,0 +1,9 @@ +import React, { ReactElement } from 'react'; + +import Layout from 'components/shared/Layout'; + +const HomeView = (): ReactElement => { + return HOME; +}; + +export default HomeView; From a71f8ae8d193db5bec78dd2422dcff463dd49806 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Miko=C5=82ajczyk?= Date: Wed, 14 Aug 2024 20:06:34 +0200 Subject: [PATCH 023/241] oct-1858: useNavigationTabs --- client/cypress/e2e/layout.cy.ts | 15 ++-- client/cypress/utils/e2e.ts | 8 +- .../src/components/shared/Layout/Layout.tsx | 62 ++++----------- .../Layout/LayoutNavbar/LayoutNavbar.tsx | 17 ++-- .../shared/Layout/LayoutNavbar/types.ts | 3 - .../LayoutTopBar/LayoutTopBar.module.scss | 2 +- .../Layout/LayoutTopBar/LayoutTopBar.tsx | 51 +++--------- .../navigationTabs/navigationTabs.ts | 69 ---------------- .../src/hooks/helpers/useNavigationTabs.tsx | 79 +++++++++++++++++++ client/src/locales/en/translation.json | 7 ++ client/src/svg/misc.ts | 6 ++ .../types.ts => types/navigationTabs.ts} | 5 +- 12 files changed, 143 insertions(+), 181 deletions(-) delete mode 100644 client/src/constants/navigationTabs/navigationTabs.ts create mode 100644 client/src/hooks/helpers/useNavigationTabs.tsx rename client/src/{constants/navigationTabs/types.ts => types/navigationTabs.ts} (76%) diff --git a/client/cypress/e2e/layout.cy.ts b/client/cypress/e2e/layout.cy.ts index 2cad93a8b9..2b3e667aee 100644 --- a/client/cypress/e2e/layout.cy.ts +++ b/client/cypress/e2e/layout.cy.ts @@ -1,3 +1,6 @@ +// TODO: https://linear.app/golemfoundation/issue/OCT-1891/e2e-layout +// import { navigationTabs } from 'src/constants/navigationTabs/navigationTabs'; + import { navigateWithCheck, mockCoinPricesServer, @@ -10,7 +13,6 @@ import { IS_ONBOARDING_ALWAYS_VISIBLE, IS_ONBOARDING_DONE, } from 'src/constants/localStorageKeys'; -import { navigationTabs } from 'src/constants/navigationTabs/navigationTabs'; import { ROOT, ROOT_ROUTES } from 'src/routes/RootRoutes/routes'; Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight }) => { @@ -69,11 +71,12 @@ Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight }) => cy.get('[data-test=Navbar]').should('be.visible'); }); - it('bottom navbar allows to change views', () => { - navigationTabs.forEach(({ to }) => { - navigateWithCheck(to); - }); - }); + // TODO: https://linear.app/golemfoundation/issue/OCT-1891/e2e-layout + // it('bottom navbar allows to change views', () => { + // navigationTabs.forEach(({ to }) => { + // navigateWithCheck(to); + // }); + // }); it('"Connect" button is visible when wallet is disconnected', () => { cy.get('[data-test=MainLayout__Button--connect]').should('be.visible'); diff --git a/client/cypress/utils/e2e.ts b/client/cypress/utils/e2e.ts index 9dd54723a8..1054089181 100644 --- a/client/cypress/utils/e2e.ts +++ b/client/cypress/utils/e2e.ts @@ -1,4 +1,6 @@ -import { navigationTabs } from 'src/constants/navigationTabs/navigationTabs'; +// TODO: https://linear.app/golemfoundation/issue/OCT-1891/e2e-layout +// import { navigationTabs } from 'src/constants/navigationTabs/navigationTabs'; + import { ROOT_ROUTES } from 'src/routes/RootRoutes/routes'; import { ConnectWalletParameters } from './types'; @@ -29,7 +31,9 @@ export const visitWithLoader = ( }; export const navigateWithCheck = (urlEnter: string): Chainable => { - const { label } = navigationTabs.find(({ to }) => to === urlEnter)!; + // TODO: https://linear.app/golemfoundation/issue/OCT-1891/e2e-layout + // const { label } = navigationTabs.find(({ to }) => to === urlEnter)!; + const label = 'Home'; cy.get(`[data-test=Navbar__Button--${label}]`).click(); return checkLocationWithLoader(urlEnter); }; diff --git a/client/src/components/shared/Layout/Layout.tsx b/client/src/components/shared/Layout/Layout.tsx index 52d52f3508..5383acc8e0 100644 --- a/client/src/components/shared/Layout/Layout.tsx +++ b/client/src/components/shared/Layout/Layout.tsx @@ -3,14 +3,14 @@ import React, { FC, // useState, Fragment, - useMemo, + // useMemo, // useEffect } from 'react'; // import { useTranslation } from 'react-i18next'; -import { - useLocation, - // useMatch, useNavigate -} from 'react-router-dom'; +// import { +// useLocation, +// useMatch, useNavigate +// } from 'react-router-dom'; // import { useAccount } from 'wagmi'; import LayoutNavbar from 'components/shared/Layout/LayoutNavbar'; @@ -18,26 +18,21 @@ import ModalLayoutConnectWallet from 'components/shared/Layout/ModalLayoutConnec import ModalLayoutWallet from 'components/shared/Layout/ModalLayoutWallet'; import Loader from 'components/ui/Loader'; import { LAYOUT_BODY_ID } from 'constants/domElementsIds'; -import { - adminNavigationTabs, - navigationTabs as navigationTabsDefault, - patronNavigationTabs, -} from 'constants/navigationTabs/navigationTabs'; // import useEpochAndAllocationTimestamps from 'hooks/helpers/useEpochAndAllocationTimestamps'; // import useGetValuesToDisplay from 'hooks/helpers/useGetValuesToDisplay'; -import useIsProjectAdminMode from 'hooks/helpers/useIsProjectAdminMode'; +// import useIsProjectAdminMode from 'hooks/helpers/useIsProjectAdminMode'; import useMediaQuery from 'hooks/helpers/useMediaQuery'; -import useCurrentEpoch from 'hooks/queries/useCurrentEpoch'; +// import useCurrentEpoch from 'hooks/queries/useCurrentEpoch'; // import useIndividualReward from 'hooks/queries/useIndividualReward'; // import useIsDecisionWindowOpen from 'hooks/queries/useIsDecisionWindowOpen'; -import useIsPatronMode from 'hooks/queries/useIsPatronMode'; +// import useIsPatronMode from 'hooks/queries/useIsPatronMode'; // import useUserTOS from 'hooks/queries/useUserTOS'; -import { ROOT_ROUTES } from 'routes/RootRoutes/routes'; +// import { ROOT_ROUTES } from 'routes/RootRoutes/routes'; import useLayoutStore from 'store/layout/store'; // import useSettingsStore from 'store/settings/store'; -import { chevronLeft } from 'svg/navigation'; +// import { chevronLeft } from 'svg/navigation'; // import getDifferenceInWeeks from 'utils/getDifferenceInWeeks'; -import getIsPreLaunch from 'utils/getIsPreLaunch'; +// import getIsPreLaunch from 'utils/getIsPreLaunch'; // import getTimeDistance from 'utils/getTimeDistance'; // import truncateEthAddress from 'utils/truncateEthAddress'; @@ -58,17 +53,17 @@ const Layout: FC = ({ // showHeaderBlur = true, }) => { const { isDesktop } = useMediaQuery(); - const { data: isPatronMode } = useIsPatronMode(); + // const { data: isPatronMode } = useIsPatronMode(); // const { i18n, t } = useTranslation('translation', { keyPrefix: 'layout.main' }); // const { address, isConnected } = useAccount(); // const { data: individualReward } = useIndividualReward(); - const { data: currentEpoch } = useCurrentEpoch(); + // const { data: currentEpoch } = useCurrentEpoch(); // const { timeCurrentAllocationEnd, timeCurrentEpochEnd } = useEpochAndAllocationTimestamps(); // const { data: isDecisionWindowOpen } = useIsDecisionWindowOpen(); - const { pathname } = useLocation(); + // const { pathname } = useLocation(); // const navigate = useNavigate(); // const { data: isUserTOSAccepted } = useUserTOS(); - const isProjectAdminMode = useIsProjectAdminMode(); + // const isProjectAdminMode = useIsProjectAdminMode(); // const { // data: { isCryptoMainValueDisplay }, // } = useSettingsStore(({ data }) => ({ @@ -90,7 +85,7 @@ const Layout: FC = ({ setShowWalletModal: state.setShowWalletModal, })); - const isPreLaunch = getIsPreLaunch(currentEpoch); + // const isPreLaunch = getIsPreLaunch(currentEpoch); // const isAllocationRoot = !!useMatch(ROOT_ROUTES.allocation.absolute); // const isUseMatchProject = !!useMatch(ROOT_ROUTES.projectWithAddress.absolute); // const isUseMatchProjectWithAddress = !!useMatch(ROOT_ROUTES.projectWithAddress.absolute); @@ -113,29 +108,6 @@ const Layout: FC = ({ // const truncatedEthAddress = useMemo(() => address && truncateEthAddress(address), [address]); - const tabsWithIsActive = useMemo(() => { - let tabs = navigationTabsDefault; - - if (isPatronMode) { - tabs = patronNavigationTabs; - } - if (isProjectAdminMode) { - tabs = adminNavigationTabs; - } - - return tabs.map(tab => { - const isProjectView = - pathname.includes(`${ROOT_ROUTES.project.absolute}/`) && - tab.to === ROOT_ROUTES.projects.absolute; - return { - ...tab, - icon: isProjectView ? chevronLeft : tab.icon, - isActive: tab.isActive || pathname === tab.to || isProjectView, - isDisabled: isPreLaunch && tab.to !== ROOT_ROUTES.earn.absolute, - }; - }); - }, [isPatronMode, isProjectAdminMode, isPreLaunch, pathname]); - // const isAllocationPeriodIsHighlighted = useMemo(() => { // if (isDecisionWindowOpen && timeCurrentAllocationEnd) { // return getDifferenceInWeeks(Date.now(), new Date(timeCurrentAllocationEnd).getTime()) < 1; @@ -298,7 +270,7 @@ const Layout: FC = ({ {isLoading ? : children} {!isDesktop && isNavigationVisible && ( - + )} diff --git a/client/src/components/shared/Layout/LayoutNavbar/LayoutNavbar.tsx b/client/src/components/shared/Layout/LayoutNavbar/LayoutNavbar.tsx index 01f741fc32..543cfe4f07 100644 --- a/client/src/components/shared/Layout/LayoutNavbar/LayoutNavbar.tsx +++ b/client/src/components/shared/Layout/LayoutNavbar/LayoutNavbar.tsx @@ -2,7 +2,6 @@ import cx from 'classnames'; import { useAnimate } from 'framer-motion'; import React, { FC, Fragment, useEffect, useRef } from 'react'; import { useLocation } from 'react-router-dom'; -import { useAccount } from 'wagmi'; import Button from 'components/ui/Button'; import Svg from 'components/ui/Svg'; @@ -10,16 +9,14 @@ import { ELEMENT_POSITION_FIXED_CLASSNAME } from 'constants/css'; import { WINDOW_PROJECTS_SCROLL_Y } from 'constants/window'; import useIsProjectAdminMode from 'hooks/helpers/useIsProjectAdminMode'; import useMediaQuery from 'hooks/helpers/useMediaQuery'; -import useUserTOS from 'hooks/queries/useUserTOS'; +import useNavigationTabs from 'hooks/helpers/useNavigationTabs'; import { ROOT_ROUTES } from 'routes/RootRoutes/routes'; import useAllocationsStore from 'store/allocations/store'; import styles from './LayoutNavbar.module.scss'; import LayoutNavbarProps from './types'; -const LayoutNavbar: FC = ({ navigationBottomSuffix, tabs }) => { - const { isConnected } = useAccount(); - const { data: isUserTOSAccepted } = useUserTOS(); +const LayoutNavbar: FC = ({ navigationBottomSuffix }) => { const { allocations } = useAllocationsStore(state => ({ allocations: state.data.allocations, })); @@ -29,8 +26,7 @@ const LayoutNavbar: FC = ({ navigationBottomSuffix, tabs }) = const location = useLocation(); const [scope, animate] = useAnimate(); const isProjectAdminMode = useIsProjectAdminMode(); - - const areTabsDisabled = isConnected && !isUserTOSAccepted; + const tabs = useNavigationTabs(); useEffect(() => { if (!scope?.current || allocations.length === allocationsPrevRef.current.length) { @@ -58,10 +54,9 @@ const LayoutNavbar: FC = ({ navigationBottomSuffix, tabs }) = className={cx(styles.buttons, isProjectAdminMode && styles.isProjectAdminMode)} data-test="Navbar__buttons" > - {tabs.map(({ icon, label, to, isActive, isDisabled = false }, index) => ( + {tabs.map(({ key, icon, label, to, isActive, isDisabled = false }) => ( - {isLargeDesktop && ( + {isDesktop && (
diff --git a/client/src/constants/navigationTabs/navigationTabs.ts b/client/src/constants/navigationTabs/navigationTabs.ts deleted file mode 100644 index 330ce14328..0000000000 --- a/client/src/constants/navigationTabs/navigationTabs.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { ROOT_ROUTES } from 'routes/RootRoutes/routes'; -import { octantInverted } from 'svg/logo'; -import { allocate, earn, metrics, project, settings } from 'svg/navigation'; - -import { NavigationTab } from './types'; - -export const navigationTabs: NavigationTab[] = [ - { - icon: octantInverted, - label: 'Home', - to: ROOT_ROUTES.home.absolute, - }, - { - icon: project, - label: 'Projects', - to: ROOT_ROUTES.projects.absolute, - }, - { - icon: allocate, - label: 'Allocate', - to: ROOT_ROUTES.allocation.absolute, - }, - { - icon: metrics, - label: 'Metrics', - to: ROOT_ROUTES.metrics.absolute, - }, - { - icon: settings, - label: 'Settings', - to: ROOT_ROUTES.settings.absolute, - }, -]; - -export const adminNavigationTabs: NavigationTab[] = [ - { - icon: earn, - label: 'Home', - to: ROOT_ROUTES.earn.absolute, - }, - { - icon: settings, - label: 'Settings', - to: ROOT_ROUTES.settings.absolute, - }, -]; - -export const patronNavigationTabs: NavigationTab[] = [ - { - icon: project, - label: 'Projects', - to: ROOT_ROUTES.projects.absolute, - }, - { - icon: earn, - label: 'Earn', - to: ROOT_ROUTES.earn.absolute, - }, - { - icon: metrics, - label: 'Metrics', - to: ROOT_ROUTES.metrics.absolute, - }, - { - icon: settings, - label: 'Settings', - to: ROOT_ROUTES.settings.absolute, - }, -]; diff --git a/client/src/hooks/helpers/useNavigationTabs.tsx b/client/src/hooks/helpers/useNavigationTabs.tsx new file mode 100644 index 0000000000..0eb2a56f32 --- /dev/null +++ b/client/src/hooks/helpers/useNavigationTabs.tsx @@ -0,0 +1,79 @@ +// import useIsPatronMode from 'hooks/queries/useIsPatronMode'; +// import useIsProjectAdminMode from './useIsProjectAdminMode'; +import { useTranslation } from 'react-i18next'; +import { useLocation } from 'react-router-dom'; +import { useAccount } from 'wagmi'; + +import useCurrentEpoch from 'hooks/queries/useCurrentEpoch'; +import useUserTOS from 'hooks/queries/useUserTOS'; +import { ROOT_ROUTES } from 'routes/RootRoutes/routes'; +import { octantInverted } from 'svg/logo'; +import { allocate, metrics, project, settings } from 'svg/navigation'; +import { NavigationTab } from 'types/navigationTabs'; +import getIsPreLaunch from 'utils/getIsPreLaunch'; + +const useNavigationTabs = (isTopBar?: boolean): NavigationTab[] => { + // TODO: project admin mode support -> https://linear.app/golemfoundation/issue/OCT-1892/layout-project-admin-mode + // const isProjectAdminMode = useIsProjectAdminMode(); + + // TODO: patron mode support -> https://linear.app/golemfoundation/issue/OCT-1893/layout-patron-mode + // const { data: isPatronMode } = useIsPatronMode(); + + const { t } = useTranslation('translation', { keyPrefix: 'layout.navigationTabs' }); + const { isConnected } = useAccount(); + const { data: isUserTOSAccepted } = useUserTOS(); + const { pathname } = useLocation(); + const { data: currentEpoch } = useCurrentEpoch(); + const isPreLaunch = getIsPreLaunch(currentEpoch); + + const navigationTabs: NavigationTab[] = [ + { + icon: octantInverted, + isActive: ROOT_ROUTES.home.absolute === pathname, + key: 'home', + label: t('home'), + to: ROOT_ROUTES.home.absolute, + }, + { + icon: project, + isActive: + ROOT_ROUTES.projects.absolute === pathname || + pathname.includes(`${ROOT_ROUTES.project.absolute}/`), + key: 'projects', + label: t('projects'), + to: ROOT_ROUTES.projects.absolute, + }, + { + icon: allocate, + isActive: ROOT_ROUTES.allocation.absolute === pathname, + key: 'allocate', + label: t('allocate'), + to: ROOT_ROUTES.allocation.absolute, + }, + { + icon: metrics, + isActive: ROOT_ROUTES.metrics.absolute === pathname, + key: 'metrics', + label: t('metrics'), + to: ROOT_ROUTES.metrics.absolute, + }, + { + icon: settings, + isActive: ROOT_ROUTES.settings.absolute === pathname, + key: 'settings', + label: t('settings'), + to: ROOT_ROUTES.settings.absolute, + }, + ].map(tab => ({ + ...tab, + isDisabled: (isConnected && !isUserTOSAccepted) || tab.key === 'home' ? false : isPreLaunch, + })); + + if (isTopBar) { + return navigationTabs.filter(({ key }) => key !== 'allocate' && key !== 'settings'); + } + + return navigationTabs; +}; + +export default useNavigationTabs; diff --git a/client/src/locales/en/translation.json b/client/src/locales/en/translation.json index e43e2bb83a..8c3cafca0a 100644 --- a/client/src/locales/en/translation.json +++ b/client/src/locales/en/translation.json @@ -300,6 +300,13 @@ } }, "layout": { + "navigationTabs": { + "home": "Home", + "projects": "Projects", + "metrics": "Metrics", + "settings": "Settings", + "allocate": "Allocate" + }, "topBar": { "epochAllocationWindowOpen": "Epoch {{epoch}} allocation window now open", "epochAllocationWindowOpenShort": "E{{epoch}} allocation open", diff --git a/client/src/svg/misc.ts b/client/src/svg/misc.ts index 7fd3730eb2..b77e0ab3cd 100644 --- a/client/src/svg/misc.ts +++ b/client/src/svg/misc.ts @@ -113,3 +113,9 @@ export const magnifyingGlass: SvgImageConfig = { '', viewBox: '0 0 32 32', }; + +export const calendar: SvgImageConfig = { + markup: + '', + viewBox: '0 0 16 16', +}; diff --git a/client/src/constants/navigationTabs/types.ts b/client/src/types/navigationTabs.ts similarity index 76% rename from client/src/constants/navigationTabs/types.ts rename to client/src/types/navigationTabs.ts index 0fe92610c5..d8802a86a0 100644 --- a/client/src/constants/navigationTabs/types.ts +++ b/client/src/types/navigationTabs.ts @@ -3,8 +3,9 @@ import { Path } from 'utils/routing'; export interface NavigationTab { icon: SvgImageConfig; - isActive?: boolean; - isDisabled?: boolean; + isActive: boolean; + isDisabled: boolean; + key: string; label: string; to: Path['absolute']; } From 778f0f04b0b5d31e0fcdf67faa52af8c8ed1de46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Garbaci=C5=84ski?= <57113816+kgarbacinski@users.noreply.github.com> Date: Wed, 14 Aug 2024 22:40:44 +0200 Subject: [PATCH 024/241] [SYNC] Master to develop (#387) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description ## Definition of Done 1. [ ] Acceptance criteria are met. 2. [ ] PR is manually tested before the merge by developer(s). - [ ] Happy path is manually checked. 3. [ ] PR is manually tested by QA when their assistance is required (1). - [ ] Octant Areas & Test Cases are checked for impact and updated if required (2). 4. [ ] Unit tests are added unless there is a reason to omit them. 5. [ ] Automated tests are added when required. 6. [ ] The code is merged. 7. [ ] Tech documentation is added / updated, reviewed and approved (including mandatory approval by a code owner, should such exist for changed files). - [ ] BE: Swagger documentation is updated. 8. [ ] When required by QA: - [ ] Deployed to the relevant environment. - [ ] Passed system tests. --- (1) Developer(s) in coordination with QA decide whether it's required. For small tickets introducing small changes QA assistance is most probably not required. (2) [Octant Areas & Test Cases](https://docs.google.com/spreadsheets/d/1cRe6dxuKJV3a4ZskAwWEPvrFkQm6rEfyUCYwLTYw_Cc). --------- Co-authored-by: Jakub Mikołajczyk Co-authored-by: Housekeeper Bot Co-authored-by: Andrzej Ziółek Co-authored-by: Jakub Mikołajczyk <38243788+jmikolajczyk@users.noreply.github.com> Co-authored-by: Pawel Peregud --- .github/workflows/ci-run.yml | 103 ++ backend/README.md | 2 + backend/app/infrastructure/events.py | 7 + backend/app/infrastructure/routes/info.py | 27 +- backend/app/legacy/controllers/info.py | 59 +- backend/tests/api-e2e/test_api_withdrawals.py | 4 - backend/tests/conftest.py | 96 +- ci/Dockerfile.subgraph | 2 +- ci/argocd/templates/octant-application.yaml | 4 + client/src/api/queryKeys/index.ts | 8 +- client/src/api/queryKeys/types.ts | 7 +- .../AllocationItemRewards.tsx | 5 +- .../AllocationTipTiles.module.scss | 16 + .../AllocationTipTiles/AllocationTipTiles.tsx | 6 +- .../EarnTipTiles/EarnTipTiles.module.scss | 27 + .../Earn/EarnTipTiles/EarnTipTiles.tsx | 22 +- .../Metrics/MetricsEpoch/MetricsEpoch.tsx | 2 +- .../MetricsPersonal.module.scss | 4 + .../MetricsPersonal/MetricsPersonal.tsx | 4 +- .../Project/ProjectDonors/ProjectDonors.tsx | 7 +- .../ProjectDonorsHeader.tsx | 11 +- .../ProjectDonorsList/ProjectDonorsList.tsx | 6 +- .../Project/ProjectList/ProjectList.tsx | 39 +- .../ProjectListItem/ProjectListItem.tsx | 13 +- .../Project/ProjectListItem/types.ts | 2 - .../shared/TipTile/TipTile.module.scss | 85 +- .../src/components/shared/TipTile/TipTile.tsx | 28 +- client/src/components/shared/TipTile/types.ts | 2 +- client/src/hooks/helpers/useSubscription.ts | 3 + .../hooks/queries/donors/useProjectDonors.ts | 60 - .../hooks/queries/donors/useProjectsDonors.ts | 88 +- .../src/hooks/queries/useEpochAllocations.ts | 2 +- client/src/locales/en/translation.json | 28 +- client/src/styles/utils/_mixins.scss | 8 + client/src/svg/misc.ts | 2 +- client/src/types/websocketEvents.ts | 4 +- .../views/AllocationView/AllocationView.tsx | 9 +- .../ProjectsView/ProjectsView.module.scss | 8 + .../src/views/ProjectsView/ProjectsView.tsx | 8 +- contracts-v1/deploy/0015_deploy_faucet.ts | 4 +- contracts-v1/deploy/0020_deploy_epochs.ts | 4 +- contracts-v1/deploy/0030_deploy_deposits.ts | 7 +- contracts-v1/deploy/0070_deploy_proposals.ts | 3 +- contracts-v1/deploy/0080_deploy_vault.ts | 3 +- contracts-v1/deploy/7000_contracts_setup.ts | 24 +- contracts-v1/deploy/9999_after_deployment.ts | 20 +- contracts-v1/helpers/misc-utils.ts | 25 + localenv/anvil/Dockerfile | 6 +- localenv/apitest.yaml | 6 + localenv/docker-compose.yaml | 5 +- localenv/localenv.yaml | 6 + localenv/multideployer/server.py | 35 +- package.json | 11 +- shell.nix | 4 +- subgraph/entrypoint.sh | 2 + subgraph/package.json | 2 +- subgraph/yarn.lock | 1636 ++++++++++------- 57 files changed, 1629 insertions(+), 992 deletions(-) create mode 100644 client/src/components/Earn/EarnTipTiles/EarnTipTiles.module.scss delete mode 100644 client/src/hooks/queries/donors/useProjectDonors.ts diff --git a/.github/workflows/ci-run.yml b/.github/workflows/ci-run.yml index feccc6ff39..9b4468cdc8 100644 --- a/.github/workflows/ci-run.yml +++ b/.github/workflows/ci-run.yml @@ -10,6 +10,109 @@ env: GITLAB_PAT_OCTANT_K8S_DEVOPS_REPOSITORY_WRITE: "${{ secrets.GITLAB_PAT_OCTANT_K8S_DEVOPS_REPOSITORY_WRITE }}" jobs: + stop-api-env: + name: Stop APITest Env + needs: + - start-apitest-env + - run-api-tests + if: always() && (needs.start-apitest-env.result == 'success') + uses: ./.github/workflows/tpl-destroy-env.yml + secrets: inherit + with: + env-type: apitest + pull-request-id: ${{ github.event.pull_request.number }} + workflow-id: ${{ github.run_id }} + + run-api-tests: + name: Run API Tests + needs: + - deploy-apitest-env + runs-on: + - general + container: + image: registry.gitlab.com/golemfoundation/devops/container-builder/octant/python-poetry-ext:ad1d9179 + credentials: + username: "doesnt-matter" + password: "${{ secrets.GITLAB_PAT_CONTAINER_BUILDER_DOCKER_IMAGES_READ }}" + env: + ENV_TYPE: apitest + CI_MERGE_REQUEST_IID: ${{ github.event.pull_request.number }} + CI_PIPELINE_ID: ${{ github.run_id }} + OCTANT_ENV: dev + CHAIN_ID: 1337 + CHAIN_NAME: localhost + GC_PASSPORT_SCORER_ID: "${{ secrets.GITCOIN_SCORER_ID }}" + GC_PASSPORT_SCORER_API_KEY: "${{ secrets.GITCOIN_SCORER_API_KEY }}" + DELEGATION_SALT: "${{ secrets.DELEGATION_SALT }}" + DELEGATION_SALT_PRIMARY: "${{ secrets.DELEGATION_SALT_PRIMARY }}" + steps: + - uses: actions/checkout@v4.1.0 + - uses: actions/cache/restore@v4 + with: + path: backend/.venv + key: "${{ github.sha }}-poetry-backend" + - name: Run API tests + run: | + set -ex + export CI_PROJECT_DIR="${GITHUB_WORKSPACE}" + + source ${CI_PROJECT_DIR}/ci/argocd/resolve_env.sh $ENV_TYPE + + pushd backend + + poetry config virtualenvs.in-project true + poetry install --no-interaction --no-ansi -v --with prod --with dev + + bash ${CI_PROJECT_DIR}/ci/argocd/wait_for_app.sh + export CONTRACTS_DEPLOYER_URL; CONTRACTS_DEPLOYER_URL=https://$(bash ${CI_PROJECT_DIR}/ci/argocd/get_multideployer_url.sh) + export ETH_RPC_PROVIDER_URL; ETH_RPC_PROVIDER_URL=https://$(bash ${CI_PROJECT_DIR}/ci/argocd/get_rpc_url.sh) + export SUBGRAPH_URL; SUBGRAPH_URL=https://$(bash ${CI_PROJECT_DIR}/ci/argocd/get_graph_url.sh) + + poetry run pytest -s --onlyapi + shell: bash + + deploy-apitest-env: + name: Deploy APITest Env + needs: + - docker + - start-apitest-env + uses: ./.github/workflows/tpl-deploy-app.yml + with: + # --- + env-type: apitest + branch-head-ref: ${{ github.ref }} + image-tag: ${{ github.sha }} + pull-request-id: ${{ github.event.pull_request.number }} + workflow-id: ${{ github.run_id }} + env-id: ${{ needs.start-apitest-env.outputs.env-id }} + deployment-id: ${{ needs.start-apitest-env.outputs.deployment-id }} + # --- + deploy-contracts: false + chain-id: 1337 + network-name: localhost + chain-name: localhost + snapshotter-enabled: false + multisigger-enabled: false + web-client-replicas: 0 + coin-prices-server-replicas: 0 + backend-server-replicas: 0 + multideployer-enabled: true + subgraph-deploy: false + graph-healtchecker-enabled: false + secrets: inherit + + start-apitest-env: + name: Start APITest Env + needs: + - permissions-check + uses: ./.github/workflows/tpl-start-env.yml + secrets: inherit + with: + env-type: apitest + git-ref: ${{ github.ref }} + pull-request-id: ${{ github.event.pull_request.number }} + workflow-id: ${{ github.run_id }} + docker: name: Docker needs: diff --git a/backend/README.md b/backend/README.md index c1a5624a68..f83fbd0747 100644 --- a/backend/README.md +++ b/backend/README.md @@ -69,6 +69,8 @@ yarn apitest:run tests/api-e2e/test_api_snapshot.py::test_pending_snapshot To stop the env, run `yarn apitest:down` +*NOTICE:* At least on Linux, anvil sometimes stops working in docker environment. This leads to multideployer timing out its startup. Restart of host machine helps. + ## Lint ```bash diff --git a/backend/app/infrastructure/events.py b/backend/app/infrastructure/events.py index 86ba0f99c0..78cc07ded7 100644 --- a/backend/app/infrastructure/events.py +++ b/backend/app/infrastructure/events.py @@ -27,6 +27,13 @@ def handle_connect(): project_rewards = get_estimated_project_rewards().rewards emit("project_rewards", _serialize_project_rewards(project_rewards)) + for project in project_rewards: + donors = controller.get_all_donations_by_project(project.address) + emit( + "project_donors", + {"project": project.address, "donors": _serialize_donors(donors)}, + ) + @socketio.on("disconnect") def handle_disconnect(): diff --git a/backend/app/infrastructure/routes/info.py b/backend/app/infrastructure/routes/info.py index 709f2601f8..6acca1441b 100644 --- a/backend/app/infrastructure/routes/info.py +++ b/backend/app/infrastructure/routes/info.py @@ -2,9 +2,9 @@ from flask import render_template, make_response, send_from_directory from flask_restx import Resource, Namespace, fields -from app.legacy.controllers import info, snapshots -from app.extensions import api, w3, epochs -from app.infrastructure import OctantResource, graphql +from app.legacy.controllers import info +from app.extensions import api +from app.infrastructure import OctantResource ns = Namespace("info", description="Information about Octant's backend API") api.add_namespace(ns) @@ -82,25 +82,10 @@ @ns.doc(description="Returns synchronization status for indexer and database") class IndexedEpoch(OctantResource): @ns.marshal_with(sync_status_model) - @ns.response(200, "Current epoch successfully retrieved") + @ns.response(200, "All services are up and syncing") + @ns.response(500, "Some services are down, sync status unknown") def get(self): - sg_result = graphql.epochs.get_epochs() - sg_epochs = sorted(sg_result["epoches"], key=lambda d: d["epoch"]) - sg_height = sg_result["_meta"]["block"]["number"] - - finalized_status = snapshots.get_finalized_snapshot_status() - pending_status = snapshots.get_pending_snapshot_status() - - b_epoch = epochs.get_current_epoch() - block = w3.eth.get_block("latest") - return { - "blockchainEpoch": b_epoch, - "indexedEpoch": sg_epochs[-1:][0]["epoch"] if sg_epochs else 0, - "blockchainHeight": block["number"], - "indexedHeight": sg_height, - "pendingSnapshot": pending_status, - "finalizedSnapshot": finalized_status, - } + return info.sync_status() @ns.route("/websockets-api") diff --git a/backend/app/legacy/controllers/info.py b/backend/app/legacy/controllers/info.py index ea6a6482c7..740372c0ac 100644 --- a/backend/app/legacy/controllers/info.py +++ b/backend/app/legacy/controllers/info.py @@ -1,9 +1,12 @@ from dataclasses import dataclass -from typing import List +from typing import List, Tuple -from app.extensions import w3 +from flask_restx.api import HTTPStatus + +from app.extensions import w3, epochs from app.infrastructure import database, graphql from app.infrastructure.exception_handler import ExceptionHandler +from app.legacy.controllers import snapshots from dataclass_wizard import JSONWizard from flask import current_app as app @@ -28,6 +31,16 @@ class Healthcheck(JSONWizard): subgraph: str +@dataclass(frozen=True) +class SyncStatus(JSONWizard): + blockchainEpoch: int | None + indexedEpoch: int | None + blockchainHeight: int | None + indexedHeight: int | None + pendingSnapshot: str | None + finalizedSnapshot: str | None + + def get_blockchain_info() -> ChainInfo: smart_contracts = [ SmartContract("Auth", app.config["AUTH_CONTRACT_ADDRESS"]), @@ -47,7 +60,7 @@ def get_blockchain_info() -> ChainInfo: ) -def healthcheck() -> (Healthcheck, int): +def healthcheck() -> Tuple[Healthcheck, int]: try: is_chain_rpc_healthy = w3.eth.chain_id == app.config["CHAIN_ID"] except Exception as e: @@ -79,6 +92,40 @@ def healthcheck() -> (Healthcheck, int): app.logger.warning( f"[Healthcheck] failed: chain_rpc: {is_chain_rpc_healthy}, db_health: {is_db_healthy}, subgraph_health: {is_subgraph_healthy}" ) - return healthcheck_status, 500 - - return healthcheck_status, 200 + return healthcheck_status, HTTPStatus.INTERNAL_SERVER_ERROR + + return healthcheck_status, HTTPStatus.OK + + +def sync_status() -> Tuple[SyncStatus, int]: + services, status = healthcheck() + blockchain_epoch = None + indexed_epoch = None + blockchain_height = None + indexed_height = None + pending_snapshot = None + finalized_snapshot = None + if services.blockchain == "UP": + blockchain_height = w3.eth.get_block("latest")["number"] + blockchain_epoch = epochs.get_current_epoch() + if services.subgraph == "UP": + sg_result = graphql.epochs.get_epochs() + sg_epochs = sorted(sg_result["epoches"], key=lambda d: d["epoch"]) + indexed_epoch = sg_epochs[-1:][0]["epoch"] if sg_epochs else 0 + indexed_height = sg_result["_meta"]["block"]["number"] + + if status == HTTPStatus.OK: + finalized_snapshot = snapshots.get_finalized_snapshot_status() + pending_snapshot = snapshots.get_pending_snapshot_status() + + return ( + SyncStatus( + blockchainEpoch=blockchain_epoch, + indexedEpoch=indexed_epoch, + blockchainHeight=blockchain_height, + indexedHeight=indexed_height, + pendingSnapshot=pending_snapshot, + finalizedSnapshot=finalized_snapshot, + ), + status, + ) diff --git a/backend/tests/api-e2e/test_api_withdrawals.py b/backend/tests/api-e2e/test_api_withdrawals.py index 4823cc3914..352f10ca3f 100644 --- a/backend/tests/api-e2e/test_api_withdrawals.py +++ b/backend/tests/api-e2e/test_api_withdrawals.py @@ -19,10 +19,6 @@ def test_withdrawals( ua_carol: UserAccount, setup_funds, ): - res, _ = client.sync_status() - assert res["indexedEpoch"] == res["blockchainEpoch"] - assert res["indexedEpoch"] > 0 - alice_proposals = get_projects_addresses(1)[:3] bob_proposals = get_projects_addresses(1)[:3] carol_proposals = get_projects_addresses(1)[:3] diff --git a/backend/tests/conftest.py b/backend/tests/conftest.py index 236b003bd2..23775e7b76 100644 --- a/backend/tests/conftest.py +++ b/backend/tests/conftest.py @@ -1,7 +1,9 @@ from __future__ import annotations import datetime +from http import HTTPStatus import json +import logging import os import time import urllib.request @@ -76,6 +78,8 @@ from tests.helpers.signature import create_multisig_signature from tests.helpers.subgraph.events import create_deposit_event +LOGGER = logging.getLogger("app") + # Contracts mocks MOCK_EPOCHS = MagicMock(spec=Epochs) MOCK_PROJECTS = MagicMock(spec=Projects) @@ -528,21 +532,31 @@ def pytest_collection_modifyitems(config, items): item.add_marker(skip_api) -def setup_deployment() -> dict[str, str]: +def setup_deployment(test_name: str) -> dict[str, str]: deployer = os.getenv("CONTRACTS_DEPLOYER_URL") - testname = f"octant_test_{random_string()}" + env_name = f"octant_test_{random_string()}" + LOGGER.debug(f"test {test_name}, environment name: {env_name}") try: - f = urllib.request.urlopen(f"{deployer}/?name={testname}") + f = urllib.request.urlopen(f"{deployer}/?name={env_name}") + time.sleep(10) deployment = f.read().decode().split("\n") deployment = {var.split("=")[0]: var.split("=")[1] for var in deployment} return deployment except urllib.error.HTTPError as err: - current_app.logger.error(f"call to multideployer failed: {err}") - current_app.logger.error(f"multideployer failed: code is {err.code}") - current_app.logger.error(f"multideployer failed: msg is {err.msg}") + LOGGER.debug(f"call to multideployer failed: {err}") + LOGGER.debug(f"multideployer failed: code is {err.code}") + LOGGER.debug(f"multideployer failed: msg is {err.msg}") raise err +def teardown_deployment(test_name, subgraph_name): + deployer = os.getenv("CONTRACTS_DEPLOYER_URL") + LOGGER.debug( + f"calling multideployer to teardown env {subgraph_name} for test {test_name}" + ) + urllib.request.urlopen(f"{deployer}/remove?name={subgraph_name}") + + def random_string() -> str: import random import string @@ -566,11 +580,11 @@ def flask_client(deployment) -> FlaskClient: @pytest.fixture(scope="function") -def deployment(pytestconfig): +def deployment(pytestconfig, request): """ Deploy contracts and a subgraph under a single-use name. """ - envs = setup_deployment() + envs = setup_deployment(request.node.name) graph_name = envs["SUBGRAPH_NAME"] conf = DevConfig graph_url = os.environ["SUBGRAPH_URL"] @@ -590,6 +604,7 @@ def deployment(pytestconfig): "0x92db14e403b83dfe3df233f83dfa3a0d7096f21ca9b0d6d6b8d88b2b4ec1564e" ) yield conf + teardown_deployment(request.node.name, graph_name) class UserAccount: @@ -681,29 +696,35 @@ def __init__(self, flask_client: FlaskClient): def root(self): return self._flask_client.get("/").text - def get_chain_info(self) -> tuple[dict, int]: - rv = self._flask_client.get("/info/chain-info") - return json.loads(rv.text), rv.status_code - - def get_healthcheck(self) -> tuple[dict, int]: - rv = self._flask_client.get("/info/healthcheck") - return json.loads(rv.text), rv.status_code - - def get_version(self) -> tuple[dict, int]: - rv = self._flask_client.get("/info/version") - return json.loads(rv.text), rv.status_code - - def sync_status(self) -> tuple[dict, int]: + def sync_status(self): rv = self._flask_client.get("/info/sync-status") return json.loads(rv.text), rv.status_code - def wait_for_sync(self, target): + def wait_for_sync(self, target, timeout_s=20, check_interval=0.5): + timeout = datetime.timedelta(seconds=timeout_s) + start = datetime.datetime.now() while True: - res, _ = self.sync_status() + try: + res, status_code = self.sync_status() + current_app.logger.debug(f"sync_status returns {res}") + current_app.logger.debug( + f"sync_status http status code is {status_code}" + ) + assert status_code == HTTPStatus.OK + except Exception as exp: + current_app.logger.warning( + f"Request to /info/sync-status returned {exp}" + ) + if datetime.datetime.now() - start > timeout: + raise TimeoutError( + f"Waiting for sync for epoch {target} has timeouted ({timeout_s} sec)" + ) + time.sleep(check_interval) + continue + if res["indexedEpoch"] == res["blockchainEpoch"]: if res["indexedEpoch"] == target: return - time.sleep(0.5) def wait_for_height_sync(self): while True: @@ -780,12 +801,8 @@ def get_locked_ratio_in_epoch(self, epoch: int): return json.loads(rv.text), rv.status_code def get_rewards_budget(self, address: str, epoch: int): - rv = self._flask_client.get(f"/rewards/budget/{address}/epoch/{epoch}").text - current_app.logger.debug( - "get_rewards_budget :", - self._flask_client.get(f"/rewards/budget/{address}/epoch/{epoch}").request, - ) - return json.loads(rv) + rv = self._flask_client.get(f"/rewards/budget/{address}/epoch/{epoch}") + return json.loads(rv.text) def get_withdrawals_for_address(self, address: str): rv = self._flask_client.get(f"/withdrawals/{address}").text @@ -896,6 +913,18 @@ def refresh_antisybil_score(self, user_address: str) -> (str | None, int): rv = self._flask_client.put(f"/user/{user_address}/antisybil-status") return rv.text, rv.status_code + def get_chain_info(self) -> tuple[dict, int]: + rv = self._flask_client.get("/info/chain-info") + return json.loads(rv.text), rv.status_code + + def get_version(self) -> tuple[dict, int]: + rv = self._flask_client.get("/info/version") + return json.loads(rv.text), rv.status_code + + def get_healthcheck(self) -> tuple[dict, int]: + rv = self._flask_client.get("/info/healthcheck") + return json.loads(rv.text), rv.status_code + @property def config(self): return self._flask_client.application.config @@ -907,12 +936,7 @@ def client(flask_client: FlaskClient) -> Client: for i in range(1, STARTING_EPOCH + 1): if i != 1: client.move_to_next_epoch(i) - while True: - res, _ = client.sync_status() - if res["indexedEpoch"] == res["blockchainEpoch"] and res["indexedEpoch"] > ( - i - 1 - ): - break + client.wait_for_sync(i, timeout_s=60) return client diff --git a/ci/Dockerfile.subgraph b/ci/Dockerfile.subgraph index 1196e10ae0..aec749acf6 100644 --- a/ci/Dockerfile.subgraph +++ b/ci/Dockerfile.subgraph @@ -1,4 +1,4 @@ -FROM local-docker-registry.wildland.dev:80/library/node:16-alpine AS root +FROM local-docker-registry.wildland.dev:80/library/node:18-alpine AS root WORKDIR /app diff --git a/ci/argocd/templates/octant-application.yaml b/ci/argocd/templates/octant-application.yaml index b06817490f..f6cb4882b0 100644 --- a/ci/argocd/templates/octant-application.yaml +++ b/ci/argocd/templates/octant-application.yaml @@ -42,6 +42,10 @@ spec: value: '$BACKEND_RPC_URL' - name: graphNode.graph.env.GRAPHQL_MAX_FIRST value: '100000' + - name: graphNode.graph.env.GRAPH_LOG + value: 'debug' + - name: graphNode.graph.env.ETHEREUM_REORG_THRESHOLD + value: '0' - name: graphHealtchecker.replicas value: '${GRAPH_HEALTHCHECKER_REPLICAS:-1}' - name: subgraph.deploy diff --git a/client/src/api/queryKeys/index.ts b/client/src/api/queryKeys/index.ts index 15da540921..1d3a11f67b 100644 --- a/client/src/api/queryKeys/index.ts +++ b/client/src/api/queryKeys/index.ts @@ -17,8 +17,8 @@ export const ROOTS: Root = { individualReward: 'individualReward', matchedProjectRewards: 'matchedProjectRewards', patronMode: 'patronMode', - projectDonors: 'projectDonors', projectRewardsThreshold: 'projectRewardsThreshold', + projectsDonors: 'projectsDonors', projectsEpoch: 'projectsEpoch', projectsIpfsResults: 'projectsIpfsResults', upcomingBudget: 'upcomingBudget', @@ -56,12 +56,8 @@ export const QUERY_KEYS: QueryKeys = { lockedSummarySnapshots: ['lockedSummarySnapshots'], matchedProjectRewards: epochNumber => [ROOTS.matchedProjectRewards, epochNumber.toString()], patronMode: userAddress => [ROOTS.patronMode, userAddress], - projectDonors: (projectAddress, epochNumber) => [ - ROOTS.projectDonors, - projectAddress, - epochNumber.toString(), - ], projectRewardsThreshold: epochNumber => [ROOTS.projectRewardsThreshold, epochNumber.toString()], + projectsDonors: epochNumber => [ROOTS.projectsDonors, epochNumber.toString()], projectsEpoch: epochNumber => [ROOTS.projectsEpoch, epochNumber.toString()], projectsIpfsResults: (projectAddress, epochNumber) => [ ROOTS.projectsIpfsResults, diff --git a/client/src/api/queryKeys/types.ts b/client/src/api/queryKeys/types.ts index 71a901c8c0..bf4f1e0851 100644 --- a/client/src/api/queryKeys/types.ts +++ b/client/src/api/queryKeys/types.ts @@ -17,8 +17,8 @@ export type Root = { individualReward: 'individualReward'; matchedProjectRewards: 'matchedProjectRewards'; patronMode: 'patronMode'; - projectDonors: 'projectDonors'; projectRewardsThreshold: 'projectRewardsThreshold'; + projectsDonors: 'projectsDonors'; projectsEpoch: 'projectsEpoch'; projectsIpfsResults: 'projectsIpfsResults'; upcomingBudget: 'upcomingBudget'; @@ -58,11 +58,8 @@ export type QueryKeys = { lockedSummarySnapshots: ['lockedSummarySnapshots']; matchedProjectRewards: (epochNumber: number) => [Root['matchedProjectRewards'], string]; patronMode: (userAddress: string) => [Root['patronMode'], string]; - projectDonors: ( - projectAddress: string, - epochNumber: number, - ) => [Root['projectDonors'], string, string]; projectRewardsThreshold: (epochNumber: number) => [Root['projectRewardsThreshold'], string]; + projectsDonors: (epochNumber: number) => [Root['projectsDonors'], string]; projectsEpoch: (epochNumber: number) => [Root['projectsEpoch'], string]; projectsIpfsResults: ( projectAddress: string, diff --git a/client/src/components/Allocation/AllocationItemRewards/AllocationItemRewards.tsx b/client/src/components/Allocation/AllocationItemRewards/AllocationItemRewards.tsx index 47192170de..93bb16dc36 100644 --- a/client/src/components/Allocation/AllocationItemRewards/AllocationItemRewards.tsx +++ b/client/src/components/Allocation/AllocationItemRewards/AllocationItemRewards.tsx @@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next'; import Svg from 'components/ui/Svg'; import useGetValuesToDisplay from 'hooks/helpers/useGetValuesToDisplay'; -import useProjectDonors from 'hooks/queries/donors/useProjectDonors'; +import useProjectsDonors from 'hooks/queries/donors/useProjectsDonors'; import useCurrentEpoch from 'hooks/queries/useCurrentEpoch'; import useIsDecisionWindowOpen from 'hooks/queries/useIsDecisionWindowOpen'; import useMatchedProjectRewards from 'hooks/queries/useMatchedProjectRewards'; @@ -107,7 +107,8 @@ const AllocationItemRewards: FC = ({ const { data: matchedProjectRewards } = useMatchedProjectRewards(); const { data: uqScore } = useUqScore(currentEpoch!); - const { data: projectDonors } = useProjectDonors(address); + const { data: projectsDonors } = useProjectsDonors(); + const projectDonors = projectsDonors?.[address]; // value can an empty string, which crashes parseUnits. Hence the alternative. const valueToUse = value || '0'; diff --git a/client/src/components/Allocation/AllocationTipTiles/AllocationTipTiles.module.scss b/client/src/components/Allocation/AllocationTipTiles/AllocationTipTiles.module.scss index 017ffbf311..46ac8138a2 100644 --- a/client/src/components/Allocation/AllocationTipTiles/AllocationTipTiles.module.scss +++ b/client/src/components/Allocation/AllocationTipTiles/AllocationTipTiles.module.scss @@ -2,3 +2,19 @@ color: $color-octant-green; font-weight: $font-weight-bold; } + +.uqTooLowImage { + height: 7.2rem; + + @media #{$desktop-up} { + height: 10rem; + } +} + +.rewardsImage { + height: 8.5rem; + + @media #{$desktop-up} { + height: 11rem; + } +} diff --git a/client/src/components/Allocation/AllocationTipTiles/AllocationTipTiles.tsx b/client/src/components/Allocation/AllocationTipTiles/AllocationTipTiles.tsx index e134d92daa..97e2fd2b3b 100644 --- a/client/src/components/Allocation/AllocationTipTiles/AllocationTipTiles.tsx +++ b/client/src/components/Allocation/AllocationTipTiles/AllocationTipTiles.tsx @@ -18,7 +18,7 @@ import styles from './AllocationTipTiles.module.scss'; import AllocationTipTilesProps from './types'; const AllocationTipTiles: FC = ({ className }) => { - const { t, i18n } = useTranslation('translation', { keyPrefix: 'views.allocation.tip' }); + const { t } = useTranslation('translation', { keyPrefix: 'views.allocation.tip' }); const navigate = useNavigate(); const { isDesktop } = useMediaQuery(); const { address, isConnected } = useAccount(); @@ -92,7 +92,7 @@ const AllocationTipTiles: FC = ({ className }) => { navigate(ROOT_ROUTES.settings.absolute)} onClose={() => setWasUqTooLowAlreadyClosed(true)} @@ -111,7 +111,7 @@ const AllocationTipTiles: FC = ({ className }) => { setWasRewardsAlreadyClosed(true)} text={isDesktop ? t('rewards.text.desktop') : t('rewards.text.mobile')} diff --git a/client/src/components/Earn/EarnTipTiles/EarnTipTiles.module.scss b/client/src/components/Earn/EarnTipTiles/EarnTipTiles.module.scss new file mode 100644 index 0000000000..ba0bfc9c8c --- /dev/null +++ b/client/src/components/Earn/EarnTipTiles/EarnTipTiles.module.scss @@ -0,0 +1,27 @@ +.lockGlmImage { + height: 10.5rem; + + @media #{$desktop-up} { + height: 11.7rem; + } +} + +.connectWalletImage { + @include tipTileConnectWalletImage(); +} + +.withdrawImage { + height: 7.8rem; + + @media #{$desktop-up} { + height: 10.4rem; + } +} + +.allocateYourRewardsImage { + height: 7.5rem; + + @media #{$desktop-up} { + height: 10.4rem; + } +} diff --git a/client/src/components/Earn/EarnTipTiles/EarnTipTiles.tsx b/client/src/components/Earn/EarnTipTiles/EarnTipTiles.tsx index 499b9925b3..b8404e488a 100644 --- a/client/src/components/Earn/EarnTipTiles/EarnTipTiles.tsx +++ b/client/src/components/Earn/EarnTipTiles/EarnTipTiles.tsx @@ -15,8 +15,10 @@ import useWithdrawals from 'hooks/queries/useWithdrawals'; import useTipsStore from 'store/tips/store'; import getIsPreLaunch from 'utils/getIsPreLaunch'; +import styles from './EarnTipTiles.module.scss'; + const EarnTipTiles = (): ReactElement => { - const { t, i18n } = useTranslation('translation', { + const { t } = useTranslation('translation', { keyPrefix: 'views.earn.tips', }); const { isConnected } = useAccount(); @@ -75,37 +77,35 @@ const EarnTipTiles = (): ReactElement => { setWasLockGLMAlreadyClosed(true)} - text={t('lockGlm.text')} + text={t(isDesktop ? 'lockGlm.text.desktop' : 'lockGlm.text.mobile')} title={t('lockGlm.title')} /> setWasConnectWalletAlreadyClosed(true)} - text={t('connectWallet.text')} + text={t(isDesktop ? 'connectWallet.text.desktop' : 'connectWallet.text.mobile')} title={t(isDesktop ? 'connectWallet.title.desktop' : 'connectWallet.title.mobile')} /> setWasWithdrawAlreadyClosed(true)} - text={t('withdrawEth.text')} + text={t(isDesktop ? 'withdrawEth.text.desktop' : 'withdrawEth.text.mobile')} title={t('withdrawEth.title')} /> setWasAllocateRewardsAlreadyClosed(true)} - text={ - isDesktop ? t('allocateYourRewards.textDesktop') : t('allocateYourRewards.textMobile') - } + text={t(isDesktop ? 'allocateYourRewards.text.desktop' : 'allocateYourRewards.text.mobile')} title={t('allocateYourRewards.title')} /> diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpoch.tsx b/client/src/components/Metrics/MetricsEpoch/MetricsEpoch.tsx index 8d7040a8ad..1f3358473e 100644 --- a/client/src/components/Metrics/MetricsEpoch/MetricsEpoch.tsx +++ b/client/src/components/Metrics/MetricsEpoch/MetricsEpoch.tsx @@ -65,7 +65,7 @@ const MetricsEpoch = (): ReactElement => { useEpochUnusedRewards(epoch); const ethBelowThreshold = - projectRewardsThreshold === undefined + projectRewardsThreshold === undefined || projectsDonors === undefined ? BigInt(0) : Object.values(projectsDonors).reduce((acc, curr) => { const projectSumOfDonations = curr.reduce((acc2, curr2) => { diff --git a/client/src/components/Metrics/MetricsPersonal/MetricsPersonal.module.scss b/client/src/components/Metrics/MetricsPersonal/MetricsPersonal.module.scss index e199d88c86..366333883e 100644 --- a/client/src/components/Metrics/MetricsPersonal/MetricsPersonal.module.scss +++ b/client/src/components/Metrics/MetricsPersonal/MetricsPersonal.module.scss @@ -12,3 +12,7 @@ background-color: $color-octant-grey1; margin: 3.2rem 0; } + +.connectWalletImage { + @include tipTileConnectWalletImage(); +} diff --git a/client/src/components/Metrics/MetricsPersonal/MetricsPersonal.tsx b/client/src/components/Metrics/MetricsPersonal/MetricsPersonal.tsx index 1c65cb3723..e4729c1be4 100644 --- a/client/src/components/Metrics/MetricsPersonal/MetricsPersonal.tsx +++ b/client/src/components/Metrics/MetricsPersonal/MetricsPersonal.tsx @@ -22,7 +22,7 @@ const MetricsPersonal = (): ReactElement => { const { isConnected } = useAccount(); const { isDesktop } = useMediaQuery(); const [isConnectWalletTipTileOpen, setIsConnectWalletTipTileOpen] = useState(!isConnected); - const { i18n, t } = useTranslation('translation', { keyPrefix: 'views.metrics' }); + const { t } = useTranslation('translation', { keyPrefix: 'views.metrics' }); const { data: { displayCurrency }, @@ -68,7 +68,7 @@ const MetricsPersonal = (): ReactElement => { setIsConnectWalletTipTileOpen(false)} text={ diff --git a/client/src/components/Project/ProjectDonors/ProjectDonors.tsx b/client/src/components/Project/ProjectDonors/ProjectDonors.tsx index 8544ced53f..8ca87b2fcb 100644 --- a/client/src/components/Project/ProjectDonors/ProjectDonors.tsx +++ b/client/src/components/Project/ProjectDonors/ProjectDonors.tsx @@ -8,7 +8,7 @@ import ProjectDonorsHeader from 'components/Project/ProjectDonorsHeader'; import ProjectDonorsList from 'components/Project/ProjectDonorsList'; import Button from 'components/ui/Button'; import { DONORS_SHORT_LIST_LENGTH } from 'constants/donors'; -import useProjectDonors from 'hooks/queries/donors/useProjectDonors'; +import useProjectsDonors from 'hooks/queries/donors/useProjectsDonors'; import useCurrentEpoch from 'hooks/queries/useCurrentEpoch'; import styles from './ProjectDonors.module.scss'; @@ -23,12 +23,11 @@ const ProjectDonors: FC = ({ const { data: currentEpoch } = useCurrentEpoch(); const epochNumber = parseInt(epoch!, 10); - const { t } = useTranslation('translation', { keyPrefix: 'components.dedicated.donors' }); - const { data: projectDonors, isFetching } = useProjectDonors( - projectAddress, + const { data: projectsDonors, isFetching } = useProjectsDonors( epochNumber === currentEpoch ? undefined : epochNumber, ); + const projectDonors = projectsDonors?.[projectAddress]; const [isFullDonorsListModalOpen, setIsFullDonorsListModalOpen] = useState(false); diff --git a/client/src/components/Project/ProjectDonorsHeader/ProjectDonorsHeader.tsx b/client/src/components/Project/ProjectDonorsHeader/ProjectDonorsHeader.tsx index f8375c38c3..3531a5f8ab 100644 --- a/client/src/components/Project/ProjectDonorsHeader/ProjectDonorsHeader.tsx +++ b/client/src/components/Project/ProjectDonorsHeader/ProjectDonorsHeader.tsx @@ -3,7 +3,7 @@ import React, { FC, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { useParams } from 'react-router-dom'; -import useProjectDonors from 'hooks/queries/donors/useProjectDonors'; +import useProjectsDonors from 'hooks/queries/donors/useProjectsDonors'; import useCurrentEpoch from 'hooks/queries/useCurrentEpoch'; import styles from './ProjectDonorsHeader.module.scss'; @@ -20,16 +20,19 @@ const ProjectDonorsHeader: FC = ({ const epochNumber = parseInt(epoch!, 10); - const { data: projectDonors, isFetching } = useProjectDonors( - projectAddress, + const { data: projectsDonors, isFetching } = useProjectsDonors( epochNumber === currentEpoch ? undefined : epochNumber, ); + const projectDonors = projectsDonors?.[projectAddress]; const numberOfDonors = useMemo(() => { if (epochNumber === currentEpoch) { return 0; } - return isFetching ? '--' : projectDonors?.length; + if (isFetching) { + return '---'; + } + return projectDonors?.length || '0'; }, [isFetching, projectDonors, epochNumber, currentEpoch]); return ( diff --git a/client/src/components/Project/ProjectDonorsList/ProjectDonorsList.tsx b/client/src/components/Project/ProjectDonorsList/ProjectDonorsList.tsx index f68f04ad39..9245a79781 100644 --- a/client/src/components/Project/ProjectDonorsList/ProjectDonorsList.tsx +++ b/client/src/components/Project/ProjectDonorsList/ProjectDonorsList.tsx @@ -6,7 +6,7 @@ import ProjectDonorsListItem from 'components/Project/ProjectDonorsListItem'; import ProjectDonorsListSkeletonItem from 'components/Project/ProjectDonorsListSkeletonItem'; import ProjectDonorsListTotalDonated from 'components/Project/ProjectDonorsListTotalDonated'; import { DONORS_SHORT_LIST_LENGTH } from 'constants/donors'; -import useProjectDonors from 'hooks/queries/donors/useProjectDonors'; +import useProjectsDonors from 'hooks/queries/donors/useProjectsDonors'; import useCurrentEpoch from 'hooks/queries/useCurrentEpoch'; import styles from './ProjectDonorsList.module.scss'; @@ -23,10 +23,10 @@ const ProjectDonorsList: FC = ({ const epochNumber = parseInt(epoch!, 10); - const { data: projectDonors, isFetching } = useProjectDonors( - projectAddress, + const { data: projectsDonors, isFetching } = useProjectsDonors( epochNumber === currentEpoch ? undefined : epochNumber, ); + const projectDonors = projectsDonors?.[projectAddress]; return (
diff --git a/client/src/components/Project/ProjectList/ProjectList.tsx b/client/src/components/Project/ProjectList/ProjectList.tsx index f5a61564ac..cc5a61461d 100644 --- a/client/src/components/Project/ProjectList/ProjectList.tsx +++ b/client/src/components/Project/ProjectList/ProjectList.tsx @@ -6,33 +6,18 @@ import ProjectListProps from './types'; const ProjectList: FC = ({ projects, epoch }) => ( <> - {projects.map( - ( - { - address, - description, - name, - profileImageSmall, - website, - totalValueOfAllocations, - numberOfDonors, - }, - index, - ) => ( - - ), - )} + {projects.map(({ address, description, name, profileImageSmall, website }, index) => ( + + ))} ); diff --git a/client/src/components/Project/ProjectListItem/ProjectListItem.tsx b/client/src/components/Project/ProjectListItem/ProjectListItem.tsx index 4b1a8c9266..45da930b03 100644 --- a/client/src/components/Project/ProjectListItem/ProjectListItem.tsx +++ b/client/src/components/Project/ProjectListItem/ProjectListItem.tsx @@ -1,4 +1,4 @@ -import React, { FC, Fragment, memo, useMemo } from 'react'; +import React, { FC, Fragment, useMemo } from 'react'; import ProjectDonors from 'components/Project/ProjectDonors'; import ProjectListItemHeader from 'components/Project/ProjectListItemHeader'; @@ -6,6 +6,7 @@ import RewardsWithoutThreshold from 'components/shared/RewardsWithoutThreshold'; import RewardsWithThreshold from 'components/shared/RewardsWithThreshold'; import Description from 'components/ui/Description'; import useCurrentEpoch from 'hooks/queries/useCurrentEpoch'; +import useProjectsIpfsWithRewards from 'hooks/queries/useProjectsIpfsWithRewards'; import decodeBase64ToUtf8 from 'utils/decodeBase64ToUtf8'; import styles from './ProjectListItem.module.scss'; @@ -19,9 +20,13 @@ const ProjectListItem: FC = ({ website, index, epoch, - totalValueOfAllocations, - numberOfDonors, }) => { + const { data: projectsIpfsWithRewards } = useProjectsIpfsWithRewards(epoch); + const projectIpfsWithRewards = projectsIpfsWithRewards.find(p => p.address === address); + // loadedProjects (ProjectView) aren't updated during changes in open AW + // to provide live updates, the following values are taken directly from projectsIpfsWithRewards + const numberOfDonors = projectIpfsWithRewards?.numberOfDonors || 0; + const totalValueOfAllocations = projectIpfsWithRewards?.totalValueOfAllocations || 0n; const { data: currentEpoch } = useCurrentEpoch(); const isEpoch1 = currentEpoch === 1; @@ -66,4 +71,4 @@ const ProjectListItem: FC = ({ ); }; -export default memo(ProjectListItem); +export default ProjectListItem; diff --git a/client/src/components/Project/ProjectListItem/types.ts b/client/src/components/Project/ProjectListItem/types.ts index 0ad72baa82..2d5f32b22e 100644 --- a/client/src/components/Project/ProjectListItem/types.ts +++ b/client/src/components/Project/ProjectListItem/types.ts @@ -4,9 +4,7 @@ export default interface ProjectListItemProps { epoch?: number; index: number; name?: string; - numberOfDonors: number; profileImageSmall?: string; - totalValueOfAllocations?: bigint; website?: { label?: string; url: string; diff --git a/client/src/components/shared/TipTile/TipTile.module.scss b/client/src/components/shared/TipTile/TipTile.module.scss index 7e6d4fc62b..f2db86de87 100644 --- a/client/src/components/shared/TipTile/TipTile.module.scss +++ b/client/src/components/shared/TipTile/TipTile.module.scss @@ -6,76 +6,83 @@ width: 100%; height: 20rem; align-items: center; + padding: 0 2.4rem; &.isClickable { cursor: pointer; } @media #{$desktop-up} { - height: 19.2rem; + height: 22.4rem; } } +.content { + display: flex; + flex-direction: column; + width: 100%; +} + +.info { + display: flex; + align-items: center; + height: 5.6rem; + border-bottom: 0.1rem solid $color-octant-grey3; +} + .infoLabel { - color: $color-octant-dark; - font-weight: $font-weight-bold; - font-size: $font-size-14; + color: $color-octant-grey5; + font-size: $font-size-12; margin-left: 0.8rem; line-height: 3.2rem; @media #{$desktop-up} { - font-size: $font-size-16; + font-size: $font-size-14; } } -.image { - max-width: 100%; - max-height: 100%; -} - -.info { +.imageWrapper { display: flex; - align-items: center; - padding: 2.4rem 0 0 2rem; + flex: 1; + justify-content: center; + max-height: 14.4rem; @media #{$desktop-up} { - padding: 2rem 0 0 2rem; + align-items: center; + width: 28rem; } } +.image { + max-width: 100%; + max-height: 100%; +} + .body { + display: flex; text-align: left; - padding: 2.5rem 0 0.8rem 2.4rem; + height: 14.4rem; + padding-top: 1.2rem; @media #{$desktop-up} { - padding: 3rem 0 4.8rem 2.4rem; - font-size: $font-size-20; + height: 16.8rem; } } +.titleAndText { + display: flex; + flex-direction: column; +} + .title { color: $color-octant-dark; font-weight: $font-weight-bold; - font-size: $font-size-18; + font-size: $font-size-16; line-height: 3.2rem; @media #{$desktop-up} { - font-size: $font-size-20; - } -} - -.imageWrapper { - display: flex; - flex: 1; - align-items: center; - justify-content: center; - padding: 4rem 2rem 1.6rem 0; - height: 100%; - - @media #{$desktop-up} { - flex: 1; - width: 28rem; - padding: 1.6rem 2rem 1.6rem 0; + font-size: $font-size-18; + line-height: 4.8rem; } } @@ -84,22 +91,18 @@ font-size: $font-size-12; font-weight: $font-weight-semibold; line-height: 2rem; - width: 19rem; - height: 7.7rem; - margin-top: 0.2rem; + width: 20rem; @media #{$desktop-up} { - margin-top: 0.6rem; flex: 1; width: 33.6rem; font-size: $font-size-14; - height: 5.6rem; line-height: 2.2rem; } } .buttonClose { position: absolute; - top: 1rem; - right: 1rem; + top: 0.8rem; + right: 0.8rem; } diff --git a/client/src/components/shared/TipTile/TipTile.tsx b/client/src/components/shared/TipTile/TipTile.tsx index 655c84d28a..745db0fccc 100644 --- a/client/src/components/shared/TipTile/TipTile.tsx +++ b/client/src/components/shared/TipTile/TipTile.tsx @@ -1,6 +1,7 @@ import cx from 'classnames'; import { AnimatePresence, motion } from 'framer-motion'; import React, { useRef } from 'react'; +import { useTranslation } from 'react-i18next'; import Button from 'components/ui/Button'; import Img from 'components/ui/Img'; @@ -16,13 +17,14 @@ const TipTile: React.FC = ({ className, dataTest = 'TipTile', image, - infoLabel, + imageClassName, isOpen, onClose, onClick, text, title, }) => { + const { t } = useTranslation('translation', { keyPrefix: 'common' }); const { isDesktop } = useMediaQuery(); const isProjectAdminMode = useIsProjectAdminMode(); const shouldSkipEntranceAnimation = useRef(isOpen); @@ -50,7 +52,13 @@ const TipTile: React.FC = ({ initial={ shouldSkipEntranceAnimation.current ? { - height: '22.4rem', + /** + * This value does not change dynamically. + * Whenever viewport changes reload of the component is required to see new height. + * This is not a huge issue as actual viewport changes does not occur often + * in real life scenarios. + */ + height: isDesktop ? '22.4rem' : '20rem', marginBottom: '1.6rem', opacity: 1, } @@ -66,19 +74,21 @@ const TipTile: React.FC = ({ stiffness: 800, }} > -
+
-
{infoLabel}
+
{t('gettingStarted')}
-
{title}
-
{text}
+
+
{title}
+
{text}
+
+
+ +
-
- -
+
+ + setIsModalLockGlmOpen(false), + }} + /> + + ); +}; + +export default HomeGridCurrentGlmLock; diff --git a/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlm/LockGlm.module.scss b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlm/LockGlm.module.scss new file mode 100644 index 0000000000..2f5b7fd1fa --- /dev/null +++ b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlm/LockGlm.module.scss @@ -0,0 +1,10 @@ +.form { + width: 100%; + height: 100%; +} + +.element { + &:not(:last-child) { + margin: 0 auto 1.6rem; + } +} diff --git a/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlm/LockGlm.tsx b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlm/LockGlm.tsx new file mode 100644 index 0000000000..64eabb9cbd --- /dev/null +++ b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlm/LockGlm.tsx @@ -0,0 +1,249 @@ +import { Formik } from 'formik'; +import React, { FC, useEffect, useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useAccount, useWalletClient, usePublicClient, useWaitForTransactionReceipt } from 'wagmi'; + +import { apiGetSafeTransactions } from 'api/calls/safeTransactions'; +import networkConfig from 'constants/networkConfig'; +import env from 'env'; +import { writeContractERC20 } from 'hooks/contracts/writeContracts'; +import useAvailableFundsGlm from 'hooks/helpers/useAvailableFundsGlm'; +import useApprovalState, { ApprovalState } from 'hooks/helpers/useMaxApproveCallback'; +import useMediaQuery from 'hooks/helpers/useMediaQuery'; +import useLock from 'hooks/mutations/useLock'; +import useUnlock from 'hooks/mutations/useUnlock'; +import useDepositValue from 'hooks/queries/useDepositValue'; +import useEstimatedEffectiveDeposit from 'hooks/queries/useEstimatedEffectiveDeposit'; +import useHistory from 'hooks/queries/useHistory'; +import useIsContract from 'hooks/queries/useIsContract'; +import useProjectsEpoch from 'hooks/queries/useProjectsEpoch'; +import useLockedSummaryLatest from 'hooks/subgraph/useLockedSummaryLatest'; +import toastService from 'services/toastService'; +import useTransactionLocalStore from 'store/transactionLocal/store'; +import { parseUnitsBigInt } from 'utils/parseUnitsBigInt'; + +import styles from './LockGlm.module.scss'; +import LockGlmProps, { Step, OnReset } from './types'; +import { formInitialValues, validationSchema } from './utils'; +import LockGlmStepper from '../LockGlmStepper'; +import LockGlmNotification from '../LockGlmNotification'; +import LockGlmBudget from '../LockGlmBudget'; +import LockGlmTabs from '../LockGlmTabs'; + +const LockGlm: FC = ({ currentMode, onCurrentModeChange, onCloseModal }) => { + const { i18n } = useTranslation(); + const { address } = useAccount(); + const publicClient = usePublicClient({ chainId: networkConfig.id }); + const { data: walletClient } = useWalletClient(); + const { isDesktop } = useMediaQuery(); + const { data: isContract } = useIsContract(); + const [transactionHashForEtherscan, setTransactionHashForEtherscan] = useState< + string | undefined + >(undefined); + const { addTransactionPending } = useTransactionLocalStore(state => ({ + addTransactionPending: state.addTransactionPending, + })); + const { data: transactionReceipt, isLoading: isLoadingTransactionReceipt } = + useWaitForTransactionReceipt({ + hash: transactionHashForEtherscan as `0x${string}` | undefined, + onReplaced: response => + setTransactionHashForEtherscan(response.transactionReceipt.transactionHash), + }); + + /** + * Value to depose so that we don't ask for allowance when user + * requests less than already approved. + */ + const [valueToDepose, setValueToDepose] = useState(BigInt(0)); + const [step, setStep] = useState(1); + const [intervalId, setIntervalId] = useState(null); + const [isCryptoOrFiatInputFocused, setIsCryptoOrFiatInputFocused] = useState(true); + const buttonUseMaxRef = useRef(null); + + const { data: availableFundsGlm } = useAvailableFundsGlm(); + const { data: projectsEpoch } = useProjectsEpoch(); + const { refetch: refetchHistory } = useHistory(); + const { data: depositsValue, refetch: refetchDeposit } = useDepositValue(); + const { refetch: refetchEstimatedEffectiveDeposit } = useEstimatedEffectiveDeposit(); + const { refetch: refetchLockedSummaryLatest } = useLockedSummaryLatest(); + + useEffect(() => { + if (transactionReceipt && !isLoadingTransactionReceipt) { + setStep(3); + } + }, [transactionReceipt, isLoadingTransactionReceipt, setStep]); + + const [approvalState, approveCallback] = useApprovalState( + address, + env.contractDepositsAddress, + valueToDepose, + ); + + const isButtonUseMaxFocused = document.activeElement === buttonUseMaxRef.current; + /** + * When input is focused isCryptoOrFiatInputFocused is true. + * Clicking "use max" blurs inputs, setting isCryptoOrFiatInputFocused to false. + * LockGlmTabs onMax sets the focus back on inputs, triggering isCryptoOrFiatInputFocused to true. + * + * Between second and third update flickering can occur, when focus is already set to input, + * but state didn't update yet. + * + * To check it out set isAnyInputFocused to permanent "false" and click "use max" fast. + */ + const isAnyInputFocused = document.activeElement?.tagName === 'INPUT'; + const showBudgetBox = + isDesktop || + (!isDesktop && !isCryptoOrFiatInputFocused && !isButtonUseMaxFocused && !isAnyInputFocused); + + const onMutate = async (): Promise => { + if (!publicClient || !walletClient || !availableFundsGlm) { + return; + } + + setStep(2); + + const approvalStateCurrent = await approveCallback(); + if (currentMode === 'lock' && approvalStateCurrent !== ApprovalState.APPROVED) { + const hash = await writeContractERC20({ + args: [env.contractDepositsAddress, availableFundsGlm.value], + functionName: 'approve', + walletClient, + }); + await publicClient.waitForTransactionReceipt({ hash }); + } + }; + + const onSuccess = async ({ hash, value }): Promise => { + if (isContract) { + const id = setInterval(async () => { + const nextSafeTransactions = await apiGetSafeTransactions(address!); + const safeTransaction = nextSafeTransactions.results.find( + t => t.safeTxHash === hash && t.transactionHash, + ); + + if (safeTransaction) { + clearInterval(id); + Promise.all([ + refetchDeposit(), + refetchEstimatedEffectiveDeposit(), + refetchLockedSummaryLatest(), + refetchHistory(), + ]).then(() => { + setTransactionHashForEtherscan(safeTransaction.transactionHash); + setStep(3); + }); + } + }, 2000); + setIntervalId(id); + return; + } + addTransactionPending({ + eventData: { + amount: value, + transactionHash: hash, + }, + isMultisig: !!isContract, + // GET /history uses seconds. Normalization here. + timestamp: Math.floor(Date.now() / 1000).toString(), + type: currentMode, + }); + setTransactionHashForEtherscan(hash); + }; + + const onReset: OnReset = ({ setFieldValue, newMode = 'lock' }) => { + onCurrentModeChange(newMode); + setTransactionHashForEtherscan(undefined); + setStep(1); + + if (setFieldValue) { + setFieldValue('currentMode', newMode); + } + }; + + const onError = () => onReset({ newMode: currentMode }); + + const lockMutation = useLock({ onError, onMutate, onSuccess }); + const unlockMutation = useUnlock({ onError, onMutate, onSuccess }); + + const onApproveOrDeposit = async ({ valueToDeposeOrWithdraw }): Promise => { + const isSignedInAsAProject = projectsEpoch!.projectsAddresses.includes(address!); + + if (isSignedInAsAProject) { + toastService.showToast({ + name: 'projectForbiddenOperation', + title: i18n.t('common.projectForbiddenOperation'), + type: 'error', + }); + return; + } + + const valueToDeposeOrWithdrawBigInt = parseUnitsBigInt(valueToDeposeOrWithdraw); + if (currentMode === 'lock') { + await lockMutation.mutateAsync(valueToDeposeOrWithdrawBigInt); + } else { + await unlockMutation.mutateAsync(valueToDeposeOrWithdrawBigInt); + } + }; + + const isLockingApproved = approvalState === ApprovalState.APPROVED; + const isApprovalKnown = approvalState !== ApprovalState.UNKNOWN; + + useEffect(() => { + return () => { + if (!intervalId) { + return; + } + clearInterval(intervalId); + }; + }, [intervalId]); + + return ( + + {props => ( +
+ {isDesktop && ( + + )} + {(step === 2 && currentMode === 'lock' && isApprovalKnown && !isLockingApproved) || + step === 3 ? ( + + ) : ( + + )} + + + )} +
+ ); +}; + +export default LockGlm; diff --git a/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlm/index.tsx b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlm/index.tsx new file mode 100644 index 0000000000..90241ca3cc --- /dev/null +++ b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlm/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './LockGlm'; diff --git a/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlm/types.ts b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlm/types.ts new file mode 100644 index 0000000000..bd8d5fe2d2 --- /dev/null +++ b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlm/types.ts @@ -0,0 +1,24 @@ +import { FormikHelpers } from 'formik'; + +export type CurrentMode = 'lock' | 'unlock'; + +export type Step = 1 | 2 | 3; + +export type FormFields = { + currentMode: CurrentMode; + valueToDeposeOrWithdraw: string; +}; + +export type OnReset = ({ + setFieldValue, + newMode, +}: { + newMode: CurrentMode; + setFieldValue?: FormikHelpers['setFieldValue']; +}) => void; + +export default interface LockGlmProps { + currentMode: CurrentMode; + onCloseModal: () => void; + onCurrentModeChange: (currentMode: CurrentMode) => void; +} diff --git a/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlm/utils.ts b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlm/utils.ts new file mode 100644 index 0000000000..01a828c963 --- /dev/null +++ b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlm/utils.ts @@ -0,0 +1,41 @@ +import { string, object, ObjectSchema } from 'yup'; + +import i18n from 'i18n'; +import { parseUnitsBigInt } from 'utils/parseUnitsBigInt'; + +import { CurrentMode, FormFields } from './types'; + +export const formInitialValues = (currentMode: CurrentMode): FormFields => ({ + currentMode, + valueToDeposeOrWithdraw: '', +}); + +export const validationSchema = ( + dataAvailableFunds: bigint | undefined, + depositsValue: bigint | undefined, +): ObjectSchema => + object().shape({ + currentMode: string().oneOf(['lock', 'unlock']).required(), + valueToDeposeOrWithdraw: string() + .required(i18n.t('common.valueCantBeEmpty')) + .test({ + name: 'value-in-range', + skipAbsent: true, + test(value, ctx) { + const { currentMode } = ctx.parent; + const newValueBigInt = parseUnitsBigInt(value || '0'); + if (currentMode === 'unlock' && newValueBigInt > depositsValue!) { + return ctx.createError({ + message: 'cantUnlock', + }); + } + if (currentMode === 'lock' && newValueBigInt > dataAvailableFunds!) { + return ctx.createError({ + message: 'dontHaveEnough', + }); + } + + return true; + }, + }), + }); diff --git a/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmBudget/LockGlmBudget.module.scss b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmBudget/LockGlmBudget.module.scss new file mode 100644 index 0000000000..2b4bc5cd05 --- /dev/null +++ b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmBudget/LockGlmBudget.module.scss @@ -0,0 +1,3 @@ +.root { + margin: 0 auto 1.6rem +} diff --git a/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmBudget/LockGlmBudget.tsx b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmBudget/LockGlmBudget.tsx new file mode 100644 index 0000000000..ef9a499efc --- /dev/null +++ b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmBudget/LockGlmBudget.tsx @@ -0,0 +1,26 @@ +import { useFormikContext } from 'formik'; +import React, { FC } from 'react'; + +import { FormFields } from 'components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlm/types'; + +import styles from './LockGlmBudget.module.scss'; +import LockGlmBudgetProps from './types'; +import LockGlmBudgetBox from '../LockGlmBudgetBox'; + +const LockGlmBudget: FC = ({ isVisible }) => { + const { errors } = useFormikContext(); + + if (!isVisible) { + return null; + } + + return ( + + ); +}; + +export default LockGlmBudget; diff --git a/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmBudget/index.tsx b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmBudget/index.tsx new file mode 100644 index 0000000000..52502f923e --- /dev/null +++ b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmBudget/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './LockGlmBudget'; diff --git a/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmBudget/types.ts b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmBudget/types.ts new file mode 100644 index 0000000000..34f7b64b31 --- /dev/null +++ b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmBudget/types.ts @@ -0,0 +1,3 @@ +export default interface LockGlmBudgetProps { + isVisible: boolean; +} diff --git a/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmBudgetBox/LockGlmBudgetBox.module.scss b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmBudgetBox/LockGlmBudgetBox.module.scss new file mode 100644 index 0000000000..8762572265 --- /dev/null +++ b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmBudgetBox/LockGlmBudgetBox.module.scss @@ -0,0 +1,65 @@ +.transactionHash { + display: flex; + justify-content: center; + margin: 1rem 0 0 0; +} + +.availableFunds { + height: 3.2rem; + display: flex; + align-items: flex-end; + border-top: 0.1rem solid $color-white; + margin-top: 1.6rem; + + .value { + color: $color-black; + + &.isError { + color: $color-octant-orange; + } + } +} + +.button { + padding: 0; +} + +.budgetRow { + position: relative; + height: 5.6rem; + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + font-size: $font-size-14; + font-weight: $font-weight-bold; + + &:not(:last-child)::after { + content: ''; + width: 100%; + position: absolute; + height: 0.1rem; + background-color: $color-octant-grey1; + bottom: 0; + left: 0; + } + + .skeleton { + @include skeleton(); + height: 1.7rem; + width: 10rem; + } + + + .budgetLabel { + color: $color-octant-grey5; + } + + .budgetValue { + color: $color-octant-dark; + + &.isError { + color: $color-octant-orange; + } + } +} diff --git a/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmBudgetBox/LockGlmBudgetBox.tsx b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmBudgetBox/LockGlmBudgetBox.tsx new file mode 100644 index 0000000000..1713e0581d --- /dev/null +++ b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmBudgetBox/LockGlmBudgetBox.tsx @@ -0,0 +1,73 @@ +import cx from 'classnames'; +import React, { FC, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; + +import BoxRounded from 'components/ui/BoxRounded'; +import useAvailableFundsGlm from 'hooks/helpers/useAvailableFundsGlm'; +import useDepositValue from 'hooks/queries/useDepositValue'; +import getFormattedGlmValue from 'utils/getFormattedGlmValue'; + +import styles from './LockGlmBudgetBox.module.scss'; +import LockGlmBudgetBoxProps from './types'; + +const LockGlmBudgetBox: FC = ({ + className, + isWalletBalanceError, + isCurrentlyLockedError, +}) => { + const { data: depositsValue, isFetching: isFetchingDepositValue } = useDepositValue(); + const { data: availableFundsGlm, isFetched: isFetchedAvailableFundsGlm } = useAvailableFundsGlm(); + + const { t } = useTranslation('translation', { + keyPrefix: 'components.home.homeGridCurrentGlmLock.modalLockGlm.lockGlmBudgetBox', + }); + + const depositsValueString = useMemo( + () => getFormattedGlmValue({ value: depositsValue || BigInt(0) }).fullString, + [depositsValue], + ); + + const availableFundsGlmString = getFormattedGlmValue({ + value: BigInt(availableFundsGlm ? availableFundsGlm!.value : 0), + }).fullString; + + return ( + +
+
{t('currentlyLocked')}
+ {isFetchingDepositValue ? ( +
+ ) : ( +
+ {depositsValueString} +
+ )} +
+
+
{t('walletBalance')}
+ {!isFetchedAvailableFundsGlm ? ( +
+ ) : ( +
+ {availableFundsGlmString} +
+ )} +
+ + ); +}; + +export default LockGlmBudgetBox; diff --git a/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmBudgetBox/index.tsx b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmBudgetBox/index.tsx new file mode 100644 index 0000000000..479b4382ca --- /dev/null +++ b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmBudgetBox/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './LockGlmBudgetBox'; diff --git a/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmBudgetBox/types.ts b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmBudgetBox/types.ts new file mode 100644 index 0000000000..182be764b7 --- /dev/null +++ b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmBudgetBox/types.ts @@ -0,0 +1,5 @@ +export default interface LockGlmBudgetBoxProps { + className?: string; + isCurrentlyLockedError?: boolean; + isWalletBalanceError?: boolean; +} diff --git a/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmNotification/LockGlmNotification.module.scss b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmNotification/LockGlmNotification.module.scss new file mode 100644 index 0000000000..8c77ef5984 --- /dev/null +++ b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmNotification/LockGlmNotification.module.scss @@ -0,0 +1,35 @@ +.notification { + display: flex; + align-items: center; +} + +.info { + height: 11.2rem; + display: flex; + flex-direction: column; + justify-content: center; + align-items: start; + font-size: $font-size-12; + line-height: 2rem; + margin-left: 2rem; + + .label { + color: $color-octant-dark; + } + + .text { + color: $color-octant-grey5; + text-align: left; + + .link { + font-size: $font-size-12; + min-height: 2rem; + font-weight: $font-weight-semibold; + + &:hover { + cursor: pointer; + transform: none; + } + } + } +} diff --git a/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmNotification/LockGlmNotification.tsx b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmNotification/LockGlmNotification.tsx new file mode 100644 index 0000000000..27c3a22084 --- /dev/null +++ b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmNotification/LockGlmNotification.tsx @@ -0,0 +1,96 @@ +import React, { FC, useMemo } from 'react'; +import { Trans, useTranslation } from 'react-i18next'; + +import BoxRounded from 'components/ui/BoxRounded'; +import Button from 'components/ui/Button'; +import Svg from 'components/ui/Svg'; +import networkConfig from 'constants/networkConfig'; +import { arrowTopRight, checkMark, notificationIconWarning } from 'svg/misc'; + +import styles from './LockGlmNotification.module.scss'; +import LockGlmNotificationProps from './types'; + +const ButtonLinkWithIcon: FC<{ children?: React.ReactNode; transactionHash: string }> = ({ + children, + transactionHash, +}) => { + return ( + + ); +}; + +const LockGlmNotification: FC = ({ + className, + isLockingApproved, + type, + transactionHash, + currentMode, +}) => { + const keyPrefix = 'components.dedicated.glmLock.glmLockNotification'; + + const { t } = useTranslation('translation', { + keyPrefix, + }); + + const label = useMemo(() => { + if (type === 'info' && currentMode === 'lock' && !isLockingApproved) { + return t('info.lock.notApproved.label'); + } + if (type === 'success' && currentMode === 'lock') { + return t('success.labelLocked'); + } + if (type === 'success' && currentMode === 'unlock') { + return t('success.labelUnlocked'); + } + }, [t, currentMode, type, isLockingApproved]); + + const text = useMemo(() => { + if (type === 'success') { + return `${keyPrefix}.success.text`; + } + if (type === 'info' && currentMode === 'lock' && !isLockingApproved) { + return `${keyPrefix}.info.lock.notApproved.text`; + } + }, [type, currentMode, isLockingApproved]); + + return ( + +
+ +
+ {label &&
{label}
} + {text && ( +
+ ] + : undefined + } + i18nKey={text} + /> +
+ )} +
+
+
+ ); +}; + +export default LockGlmNotification; diff --git a/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmNotification/index.tsx b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmNotification/index.tsx new file mode 100644 index 0000000000..c72b658630 --- /dev/null +++ b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmNotification/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './LockGlmNotification'; diff --git a/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmNotification/types.ts b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmNotification/types.ts new file mode 100644 index 0000000000..bd4f878d22 --- /dev/null +++ b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmNotification/types.ts @@ -0,0 +1,9 @@ +import { CurrentMode } from 'components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlm/types'; + +export default interface LockGlmNotificationProps { + className?: string; + currentMode: CurrentMode; + isLockingApproved: boolean; + transactionHash?: string; + type: 'success' | 'info'; +} diff --git a/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmStepper/LockGlmStepper.tsx b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmStepper/LockGlmStepper.tsx new file mode 100644 index 0000000000..8efd0b967b --- /dev/null +++ b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmStepper/LockGlmStepper.tsx @@ -0,0 +1,32 @@ +import { useFormikContext } from 'formik'; +import React, { FC } from 'react'; +import { useTranslation } from 'react-i18next'; + +import { FormFields } from 'components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlm/types'; +import BoxRounded from 'components/ui/BoxRounded'; +import ProgressStepper from 'components/ui/ProgressStepper'; + +import LockGlmStepperProps from './types'; + +const LockGlmStepper: FC = ({ currentMode, step, className }) => { + const { t, i18n } = useTranslation('translation', { + keyPrefix: 'components.dedicated.glmLock', + }); + const { isValid } = useFormikContext(); + + return ( + + + + ); +}; + +export default LockGlmStepper; diff --git a/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmStepper/index.tsx b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmStepper/index.tsx new file mode 100644 index 0000000000..c34128c49f --- /dev/null +++ b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmStepper/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './LockGlmStepper'; diff --git a/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmStepper/types.ts b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmStepper/types.ts new file mode 100644 index 0000000000..6c5ded6a40 --- /dev/null +++ b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmStepper/types.ts @@ -0,0 +1,7 @@ +import { CurrentMode } from 'components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlm/types'; + +export default interface LockGlmStepperProps { + className?: string; + currentMode: CurrentMode; + step: 1 | 2 | 3; +} diff --git a/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmTabs/LockGlmTabs.module.scss b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmTabs/LockGlmTabs.module.scss new file mode 100644 index 0000000000..067a9b9fbe --- /dev/null +++ b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmTabs/LockGlmTabs.module.scss @@ -0,0 +1,52 @@ +.box { + position: relative; + padding: 1.8rem 2.4rem 2.4rem; + + .max { + position: absolute; + font-size: $font-size-14; + line-height: 2rem; + top: 1.8rem; + right: 2.4rem; + color: $color-octant-green; + cursor: pointer; + + &.isDisabled { + opacity: 0.3; + cursor: default; + } + + &:not(.isDisabled):hover { + color: $color-octant-green2; + } + } +} + +.inputsLabel { + display: flex; + justify-content: space-between; + + .inputsLabelBalance { + display: flex; + + .availableValue { + margin: 0 0.2rem; + + &.dontHaveEnough { + color: $color-octant-orange; + } + } + + .lockedValue { + margin-right: 0.2rem; + + &.cantUnlock { + color: $color-octant-orange; + } + } + } +} + +.button { + width: 100%; +} diff --git a/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmTabs/LockGlmTabs.tsx b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmTabs/LockGlmTabs.tsx new file mode 100644 index 0000000000..d3926ab192 --- /dev/null +++ b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmTabs/LockGlmTabs.tsx @@ -0,0 +1,177 @@ +import cx from 'classnames'; +import { useFormikContext } from 'formik'; +import React, { FC, useMemo, useRef } from 'react'; +import { useTranslation } from 'react-i18next'; + +import { FormFields } from 'components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlm/types'; +import BoxRounded from 'components/ui/BoxRounded'; +import Button from 'components/ui/Button'; +import ButtonProps from 'components/ui/Button/types'; +import useAvailableFundsGlm from 'hooks/helpers/useAvailableFundsGlm'; +import useDepositValue from 'hooks/queries/useDepositValue'; +import { formatUnitsBigInt } from 'utils/formatUnitsBigInt'; +import getFormattedGlmValue from 'utils/getFormattedGlmValue'; +import { parseUnitsBigInt } from 'utils/parseUnitsBigInt'; + +import styles from './LockGlmTabs.module.scss'; +import LockGlmTabsProps from './types'; +import LockGlmTabsInputs from '../LockGlmTabsInputs'; + +const LockGlmTabs: FC = ({ + buttonUseMaxRef, + className, + currentMode, + isLoading, + onClose, + onInputsFocusChange, + onReset, + setFieldValue, + setValueToDepose, + showBalances, + step, +}) => { + const { i18n, t } = useTranslation('translation', { + keyPrefix: 'components.dedicated.glmLock', + }); + const formik = useFormikContext(); + const inputRef = useRef(null); + + const { data: availableFundsGlm } = useAvailableFundsGlm(); + const { data: depositsValue } = useDepositValue(); + + const isMaxDisabled = isLoading || step > 1; + + const onSetValue = (value: string): void => { + formik.setFieldValue('valueToDeposeOrWithdraw', value); + setValueToDepose(value ? parseUnitsBigInt(value) : BigInt(0)); + }; + + const onMax = () => { + if (isMaxDisabled || depositsValue === undefined || !availableFundsGlm) { + return; + } + const value = + currentMode === 'lock' + ? formatUnitsBigInt(BigInt(availableFundsGlm.value)) + : formatUnitsBigInt(depositsValue); + + onSetValue(value); + inputRef.current?.focus(); + }; + + const buttonCtaProps: ButtonProps = + step === 3 + ? { + onClick: onClose, + type: 'button', + } + : { + type: 'submit', + }; + + const buttonLabel = useMemo(() => { + if (isLoading) { + return i18n.t('common.waitingForConfirmation'); + } + if (step === 3) { + return i18n.t('common.close'); + } + if (currentMode === 'unlock') { + return t('unlock'); + } + return t('lock'); + }, [currentMode, step, t, isLoading, i18n]); + + const isButtonDisabled = + !formik.isValid || parseUnitsBigInt(formik.values.valueToDeposeOrWithdraw || '0') === 0n; + + return ( + onReset({ newMode: 'lock', setFieldValue }), + title: t('lock'), + }, + { + isActive: currentMode === 'unlock', + isDisabled: isLoading, + onClick: () => onReset({ newMode: 'unlock', setFieldValue }), + title: t('unlock'), + }, + ]} + > + + + {t(currentMode === 'lock' ? 'glmLockTabs.amountToLock' : 'glmLockTabs.amountToUnlock')} + {showBalances && ( +
+
+ {getFormattedGlmValue({ value: depositsValue || BigInt(0) }).value} +
+ {t('glmLockTabs.locked')} +
+ { + getFormattedGlmValue({ + value: BigInt(availableFundsGlm ? availableFundsGlm?.value : 0), + }).value + } +
+ {i18n.t('common.available')} +
+ )} +
+ } + mode={currentMode} + onChange={onSetValue} + onInputsFocusChange={onInputsFocusChange} + /> + + )} +
+ + setIsModalWithdrawEthOpen(false), + }} + /> + + ); +}; + +export default HomeGridPersonalAllocation; diff --git a/client/src/components/Home/HomeGridPersonalAllocation/ModalWithdrawEth/ModalWithdrawEth.tsx b/client/src/components/Home/HomeGridPersonalAllocation/ModalWithdrawEth/ModalWithdrawEth.tsx new file mode 100644 index 0000000000..ccc656a6b7 --- /dev/null +++ b/client/src/components/Home/HomeGridPersonalAllocation/ModalWithdrawEth/ModalWithdrawEth.tsx @@ -0,0 +1,21 @@ +import React, { FC } from 'react'; +import { useTranslation } from 'react-i18next'; + +import Modal from 'components/ui/Modal'; + +import ModalWithdrawEthProps from './types'; +import WithdrawEth from './WithdrawEth'; + +const ModalWithdrawEth: FC = ({ modalProps }) => { + const { t } = useTranslation('translation', { + keyPrefix: 'components.home.homeGridPersonalAllocation.modalWithdrawEth', + }); + + return ( + + + + ); +}; + +export default ModalWithdrawEth; diff --git a/client/src/components/Home/HomeGridPersonalAllocation/ModalWithdrawEth/WithdrawEth/WithdrawEth.module.scss b/client/src/components/Home/HomeGridPersonalAllocation/ModalWithdrawEth/WithdrawEth/WithdrawEth.module.scss new file mode 100644 index 0000000000..067ce0ebaf --- /dev/null +++ b/client/src/components/Home/HomeGridPersonalAllocation/ModalWithdrawEth/WithdrawEth/WithdrawEth.module.scss @@ -0,0 +1,20 @@ +.root { + width: 100%; + height: 100%; +} + +.inputs { + display: flex; + justify-content: space-between; + align-items: flex-end; + width: 100%; +} + +.input { + @include flexBasisGutter(2, 1.6rem); +} + +.button { + width: 100%; + margin: 2.4rem 0; +} diff --git a/client/src/components/Home/HomeGridPersonalAllocation/ModalWithdrawEth/WithdrawEth/WithdrawEth.tsx b/client/src/components/Home/HomeGridPersonalAllocation/ModalWithdrawEth/WithdrawEth/WithdrawEth.tsx new file mode 100644 index 0000000000..d20b477d6d --- /dev/null +++ b/client/src/components/Home/HomeGridPersonalAllocation/ModalWithdrawEth/WithdrawEth/WithdrawEth.tsx @@ -0,0 +1,101 @@ +import React, { FC } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useFeeData } from 'wagmi'; + +import BoxRounded from 'components/ui/BoxRounded'; +import Sections from 'components/ui/BoxRounded/Sections/Sections'; +import { SectionProps } from 'components/ui/BoxRounded/Sections/types'; +import Button from 'components/ui/Button'; +import useWithdrawEth, { BatchWithdrawRequest } from 'hooks/mutations/useWithdrawEth'; +import useWithdrawals from 'hooks/queries/useWithdrawals'; +import useTransactionLocalStore from 'store/transactionLocal/store'; + +import WithdrawEthProps from './types'; +import styles from './WithdrawEth.module.scss'; + +const WithdrawEth: FC = ({ onCloseModal }) => { + const { i18n, t } = useTranslation('translation', { + keyPrefix: 'components.home.homeGridPersonalAllocation.modalWithdrawEth', + }); + const { data: feeData, isFetching: isFetchingFeeData } = useFeeData(); + const { isAppWaitingForTransactionToBeIndexed, addTransactionPending } = useTransactionLocalStore( + state => ({ + addTransactionPending: state.addTransactionPending, + isAppWaitingForTransactionToBeIndexed: state.data.isAppWaitingForTransactionToBeIndexed, + }), + ); + const { data: withdrawals, isFetching: isWithdrawableRewardsFetching } = useWithdrawals(); + const withdrawEthMutation = useWithdrawEth(); + + const withdrawEth = async () => { + if (!withdrawals?.withdrawalsAvailable.length) { + return; + } + const value: BatchWithdrawRequest[] = withdrawals.withdrawalsAvailable.map( + ({ amount, epoch, proof }) => ({ + amount: BigInt(amount), + epoch: BigInt(epoch.toString()), + proof, + }), + ); + const { hash } = await withdrawEthMutation.mutateAsync(value); + addTransactionPending({ + eventData: { + amount: withdrawals.sums.available, + transactionHash: hash, + }, + // GET /history uses seconds. Normalization here. + timestamp: Math.floor(Date.now() / 1000).toString(), + type: 'withdrawal', + }); + onCloseModal(); + }; + + const sections: SectionProps[] = [ + { + doubleValueProps: { + cryptoCurrency: 'ethereum', + isFetching: isWithdrawableRewardsFetching || isAppWaitingForTransactionToBeIndexed, + showCryptoSuffix: true, + valueCrypto: withdrawals?.sums.available, + }, + label: t('amount'), + }, + { + doubleValueProps: { + cryptoCurrency: 'ethereum', + isFetching: isFetchingFeeData, + showCryptoSuffix: true, + valueCrypto: BigInt(feeData?.gasPrice ?? 0), + }, + label: t('estimatedGasPrice'), + }, + ]; + + return ( +
+ + +
+ ); +}; + +export default WithdrawEth; diff --git a/client/src/components/Home/HomeGridPersonalAllocation/ModalWithdrawEth/WithdrawEth/index.tsx b/client/src/components/Home/HomeGridPersonalAllocation/ModalWithdrawEth/WithdrawEth/index.tsx new file mode 100644 index 0000000000..3edc407a82 --- /dev/null +++ b/client/src/components/Home/HomeGridPersonalAllocation/ModalWithdrawEth/WithdrawEth/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './WithdrawEth'; diff --git a/client/src/components/Home/HomeGridPersonalAllocation/ModalWithdrawEth/WithdrawEth/types.ts b/client/src/components/Home/HomeGridPersonalAllocation/ModalWithdrawEth/WithdrawEth/types.ts new file mode 100644 index 0000000000..27534f8f02 --- /dev/null +++ b/client/src/components/Home/HomeGridPersonalAllocation/ModalWithdrawEth/WithdrawEth/types.ts @@ -0,0 +1,3 @@ +export default interface WithdrawEthProps { + onCloseModal: () => void; +} diff --git a/client/src/components/Home/HomeGridPersonalAllocation/ModalWithdrawEth/index.tsx b/client/src/components/Home/HomeGridPersonalAllocation/ModalWithdrawEth/index.tsx new file mode 100644 index 0000000000..ebe9ae0e5e --- /dev/null +++ b/client/src/components/Home/HomeGridPersonalAllocation/ModalWithdrawEth/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './ModalWithdrawEth'; diff --git a/client/src/components/Home/HomeGridPersonalAllocation/ModalWithdrawEth/types.ts b/client/src/components/Home/HomeGridPersonalAllocation/ModalWithdrawEth/types.ts new file mode 100644 index 0000000000..80321ff807 --- /dev/null +++ b/client/src/components/Home/HomeGridPersonalAllocation/ModalWithdrawEth/types.ts @@ -0,0 +1,5 @@ +import ModalProps from 'components/ui/Modal/types'; + +export default interface ModalWithdrawEthProps { + modalProps: Omit; +} diff --git a/client/src/components/Home/HomeGridPersonalAllocation/index.tsx b/client/src/components/Home/HomeGridPersonalAllocation/index.tsx new file mode 100644 index 0000000000..938be5e444 --- /dev/null +++ b/client/src/components/Home/HomeGridPersonalAllocation/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './HomeGridPersonalAllocation'; diff --git a/client/src/components/Home/HomeGridPersonalAllocation/types.ts b/client/src/components/Home/HomeGridPersonalAllocation/types.ts new file mode 100644 index 0000000000..98d5acb7f5 --- /dev/null +++ b/client/src/components/Home/HomeGridPersonalAllocation/types.ts @@ -0,0 +1,3 @@ +export default interface HomeGridPersonalAllocationProps { + className?: string; +} diff --git a/client/src/locales/en/translation.json b/client/src/locales/en/translation.json index d277e605e3..e7902dcc5d 100644 --- a/client/src/locales/en/translation.json +++ b/client/src/locales/en/translation.json @@ -83,6 +83,22 @@ "viewOnEtherscan": "View on Etherscan" } } + }, + "homeGridPersonalAllocation": { + "personalAllocation":"Personal allocation", + "patronEarnings": "Patron earnings", + "pending": "Pending", + "withdrawToWallet": "Withdraw to wallet", + "pendingFundsAvailableAfter": "Pending funds available after", + "currentEpoch": "Current epoch", + "modalWithdrawEth": { + "withdrawETH":"Withdraw ETH", + "withdrawalsDistributedEpoch": "Withdrawals are distributed when Epoch {{currentEpoch}} ends", + "rewardsBudget": "Rewards Budget", + "amount": "Amount", + "estimatedGasPrice": "Est. gas price", + "withdrawAll": "Withdraw all" + } } }, "settings": { From 077ed9009360e9d6b8bdc8f875948ade2618a913 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20S=C5=82omnicki?= Date: Thu, 29 Aug 2024 13:02:20 +0200 Subject: [PATCH 048/241] Bump argo app to 0.2.64 --- ci/argocd/templates/octant-application.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/argocd/templates/octant-application.yaml b/ci/argocd/templates/octant-application.yaml index f5ffc85aa4..7d840e4c3c 100644 --- a/ci/argocd/templates/octant-application.yaml +++ b/ci/argocd/templates/octant-application.yaml @@ -15,7 +15,7 @@ spec: namespace: $DEPLOYMENT_ID sources: - repoURL: 'https://gitlab.com/api/v4/projects/48137258/packages/helm/devel' - targetRevision: 0.2.63 + targetRevision: 0.2.64 chart: octant helm: parameters: From 15487bf43317ca122ec764fe4023f57b20de4431 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Miko=C5=82ajczyk?= Date: Thu, 29 Aug 2024 13:49:32 +0200 Subject: [PATCH 049/241] oct-1874: home uq score --- .../src/components/Home/HomeGrid/HomeGrid.tsx | 19 ++- .../AddressScore/AddressScore.module.scss} | 0 .../AddressScore/AddressScore.tsx} | 12 +- .../HomeGridUQScore/AddressScore}/index.tsx | 2 +- .../HomeGridUQScore/AddressScore}/types.ts | 2 +- .../HomeGridUQScore.module.scss | 35 +++++ .../HomeGridUQScore/HomeGridUQScore.tsx} | 133 +++++++++--------- .../HomeGridUQScoreAddresses.module.scss} | 6 +- .../HomeGridUQScoreAddresses.tsx} | 14 +- .../HomeGridUQScoreAddresses/index.tsx | 2 + .../HomeGridUQScoreAddresses/types.ts | 3 + .../CalculatingUQScore.module.scss} | 0 .../CalculatingUQScore.tsx} | 29 ++-- .../CalculatingUQScore}/index.tsx | 2 +- .../CalculatingUQScore}/types.ts | 0 .../ModalCalculatingUQScore.module.scss} | 0 .../ModalCalculatingUQScore.tsx} | 24 ++-- .../ModalCalculatingUQScore/index.tsx | 2 + .../ModalCalculatingUQScore}/types.ts | 0 ...odalCalculatingYourUniqueness.module.scss} | 0 .../ModalCalculatingYourUniqueness.tsx} | 23 +-- .../ModalCalculatingYourUniqueness/index.tsx | 2 + .../ModalCalculatingYourUniqueness}/types.ts | 2 +- .../ModalRecalculatingScore.module.scss} | 0 .../ModalRecalculatingScore.tsx | 27 ++++ .../RecalculatingScore.tsx} | 19 +-- .../RecalculatingScore/index.tsx | 2 + .../RecalculatingScore/types.ts | 3 + .../ModalRecalculatingScore/index.tsx | 2 + .../ModalRecalculatingScore}/types.ts | 2 +- .../ProgressPath/ProgressPath.module.scss} | 0 .../ProgressPath/ProgressPath.tsx} | 12 +- .../HomeGridUQScore/ProgressPath/index.tsx | 2 + .../HomeGridUQScore/ProgressPath/types.ts | 3 + .../components/Home/HomeGridUQScore/index.tsx | 2 + .../components/Home/HomeGridUQScore/types.ts | 3 + .../ModalSettingsCalculatingUQScore/index.tsx | 2 - .../index.tsx | 2 - .../ModalSettingsRecalculatingScore.tsx | 27 ---- .../ModalSettingsRecalculatingScore/index.tsx | 2 - .../SettingsCalculatingUQScore/index.tsx | 2 - .../Settings/SettingsProgressPath/types.ts | 3 - .../SettingsRecalculatingScore/index.tsx | 2 - .../SettingsRecalculatingScore/types.ts | 3 - .../index.tsx | 2 - .../SettingsUniquenessScoreAddresses/types.ts | 3 - .../SettingsUniquenessScoreBox.module.scss | 38 ----- .../SettingsUniquenessScoreBox/index.tsx | 2 - client/src/constants/localStorageKeys.ts | 25 +++- client/src/locales/en/translation.json | 33 +++++ client/src/store/delegation/store.ts | 101 +++++++++++++ client/src/store/delegation/types.ts | 29 ++++ .../src/views/SettingsView/SettingsView.tsx | 2 - 53 files changed, 426 insertions(+), 241 deletions(-) rename client/src/components/{Settings/SettingsAddressScore/SettingsAddressScore.module.scss => Home/HomeGridUQScore/AddressScore/AddressScore.module.scss} (100%) rename client/src/components/{Settings/SettingsAddressScore/SettingsAddressScore.tsx => Home/HomeGridUQScore/AddressScore/AddressScore.tsx} (89%) rename client/src/components/{Settings/SettingsProgressPath => Home/HomeGridUQScore/AddressScore}/index.tsx (50%) rename client/src/components/{Settings/SettingsAddressScore => Home/HomeGridUQScore/AddressScore}/types.ts (85%) create mode 100644 client/src/components/Home/HomeGridUQScore/HomeGridUQScore.module.scss rename client/src/components/{Settings/SettingsUniquenessScoreBox/SettingsUniquenessScoreBox.tsx => Home/HomeGridUQScore/HomeGridUQScore.tsx} (73%) rename client/src/components/{Settings/SettingsUniquenessScoreAddresses/SettingsUniquenessScoreAddresses.module.scss => Home/HomeGridUQScore/HomeGridUQScoreAddresses/HomeGridUQScoreAddresses.module.scss} (94%) rename client/src/components/{Settings/SettingsUniquenessScoreAddresses/SettingsUniquenessScoreAddresses.tsx => Home/HomeGridUQScore/HomeGridUQScoreAddresses/HomeGridUQScoreAddresses.tsx} (89%) create mode 100644 client/src/components/Home/HomeGridUQScore/HomeGridUQScoreAddresses/index.tsx create mode 100644 client/src/components/Home/HomeGridUQScore/HomeGridUQScoreAddresses/types.ts rename client/src/components/{Settings/SettingsCalculatingUQScore/SettingsCalculatingUQScore.module.scss => Home/HomeGridUQScore/ModalCalculatingUQScore/CalculatingUQScore/CalculatingUQScore.module.scss} (100%) rename client/src/components/{Settings/SettingsCalculatingUQScore/SettingsCalculatingUQScore.tsx => Home/HomeGridUQScore/ModalCalculatingUQScore/CalculatingUQScore/CalculatingUQScore.tsx} (90%) rename client/src/components/{Settings/SettingsAddressScore => Home/HomeGridUQScore/ModalCalculatingUQScore/CalculatingUQScore}/index.tsx (50%) rename client/src/components/{Settings/SettingsCalculatingUQScore => Home/HomeGridUQScore/ModalCalculatingUQScore/CalculatingUQScore}/types.ts (100%) rename client/src/components/{Settings/ModalSettingsCalculatingUQScore/ModalSettingsCalculatingUQScore.module.scss => Home/HomeGridUQScore/ModalCalculatingUQScore/ModalCalculatingUQScore.module.scss} (100%) rename client/src/components/{Settings/ModalSettingsCalculatingUQScore/ModalSettingsCalculatingUQScore.tsx => Home/HomeGridUQScore/ModalCalculatingUQScore/ModalCalculatingUQScore.tsx} (61%) create mode 100644 client/src/components/Home/HomeGridUQScore/ModalCalculatingUQScore/index.tsx rename client/src/components/{Settings/ModalSettingsCalculatingUQScore => Home/HomeGridUQScore/ModalCalculatingUQScore}/types.ts (100%) rename client/src/components/{Settings/ModalSettingsCalculatingYourUniqueness/ModalSettingsCalculatingYourUniqueness.module.scss => Home/HomeGridUQScore/ModalCalculatingYourUniqueness/ModalCalculatingYourUniqueness.module.scss} (100%) rename client/src/components/{Settings/ModalSettingsCalculatingYourUniqueness/ModalSettingsCalculatingYourUniqueness.tsx => Home/HomeGridUQScore/ModalCalculatingYourUniqueness/ModalCalculatingYourUniqueness.tsx} (73%) create mode 100644 client/src/components/Home/HomeGridUQScore/ModalCalculatingYourUniqueness/index.tsx rename client/src/components/{Settings/ModalSettingsRecalculatingScore => Home/HomeGridUQScore/ModalCalculatingYourUniqueness}/types.ts (63%) rename client/src/components/{Settings/ModalSettingsRecalculatingScore/ModalSettingsRecalculatingScore.module.scss => Home/HomeGridUQScore/ModalRecalculatingScore/ModalRecalculatingScore.module.scss} (100%) create mode 100644 client/src/components/Home/HomeGridUQScore/ModalRecalculatingScore/ModalRecalculatingScore.tsx rename client/src/components/{Settings/SettingsRecalculatingScore/SettingsRecalculatingScore.tsx => Home/HomeGridUQScore/ModalRecalculatingScore/RecalculatingScore/RecalculatingScore.tsx} (87%) create mode 100644 client/src/components/Home/HomeGridUQScore/ModalRecalculatingScore/RecalculatingScore/index.tsx create mode 100644 client/src/components/Home/HomeGridUQScore/ModalRecalculatingScore/RecalculatingScore/types.ts create mode 100644 client/src/components/Home/HomeGridUQScore/ModalRecalculatingScore/index.tsx rename client/src/components/{Settings/ModalSettingsCalculatingYourUniqueness => Home/HomeGridUQScore/ModalRecalculatingScore}/types.ts (60%) rename client/src/components/{Settings/SettingsProgressPath/SettingsProgressPath.module.scss => Home/HomeGridUQScore/ProgressPath/ProgressPath.module.scss} (100%) rename client/src/components/{Settings/SettingsProgressPath/SettingsProgressPath.tsx => Home/HomeGridUQScore/ProgressPath/ProgressPath.tsx} (84%) create mode 100644 client/src/components/Home/HomeGridUQScore/ProgressPath/index.tsx create mode 100644 client/src/components/Home/HomeGridUQScore/ProgressPath/types.ts create mode 100644 client/src/components/Home/HomeGridUQScore/index.tsx create mode 100644 client/src/components/Home/HomeGridUQScore/types.ts delete mode 100644 client/src/components/Settings/ModalSettingsCalculatingUQScore/index.tsx delete mode 100644 client/src/components/Settings/ModalSettingsCalculatingYourUniqueness/index.tsx delete mode 100644 client/src/components/Settings/ModalSettingsRecalculatingScore/ModalSettingsRecalculatingScore.tsx delete mode 100644 client/src/components/Settings/ModalSettingsRecalculatingScore/index.tsx delete mode 100644 client/src/components/Settings/SettingsCalculatingUQScore/index.tsx delete mode 100644 client/src/components/Settings/SettingsProgressPath/types.ts delete mode 100644 client/src/components/Settings/SettingsRecalculatingScore/index.tsx delete mode 100644 client/src/components/Settings/SettingsRecalculatingScore/types.ts delete mode 100644 client/src/components/Settings/SettingsUniquenessScoreAddresses/index.tsx delete mode 100644 client/src/components/Settings/SettingsUniquenessScoreAddresses/types.ts delete mode 100644 client/src/components/Settings/SettingsUniquenessScoreBox/SettingsUniquenessScoreBox.module.scss delete mode 100644 client/src/components/Settings/SettingsUniquenessScoreBox/index.tsx create mode 100644 client/src/store/delegation/store.ts create mode 100644 client/src/store/delegation/types.ts diff --git a/client/src/components/Home/HomeGrid/HomeGrid.tsx b/client/src/components/Home/HomeGrid/HomeGrid.tsx index 9b3a9ce38c..6ecb9dfd58 100644 --- a/client/src/components/Home/HomeGrid/HomeGrid.tsx +++ b/client/src/components/Home/HomeGrid/HomeGrid.tsx @@ -1,4 +1,5 @@ import React, { memo, ReactNode } from 'react'; +import { useAccount } from 'wagmi'; import Grid from 'components/shared/Grid'; @@ -6,12 +7,18 @@ import styles from './HomeGrid.module.scss'; import HomeGridCurrentGlmLock from '../HomeGridCurrentGlmLock'; import HomeGridPersonalAllocation from '../HomeGridPersonalAllocation'; +import HomeGridUQScore from '../HomeGridUQScore'; -const HomeGrid = (): ReactNode => ( - - - - -); +const HomeGrid = (): ReactNode => { + const { isConnected } = useAccount(); + + return ( + + + + {isConnected && } + + ); +}; export default memo(HomeGrid); diff --git a/client/src/components/Settings/SettingsAddressScore/SettingsAddressScore.module.scss b/client/src/components/Home/HomeGridUQScore/AddressScore/AddressScore.module.scss similarity index 100% rename from client/src/components/Settings/SettingsAddressScore/SettingsAddressScore.module.scss rename to client/src/components/Home/HomeGridUQScore/AddressScore/AddressScore.module.scss diff --git a/client/src/components/Settings/SettingsAddressScore/SettingsAddressScore.tsx b/client/src/components/Home/HomeGridUQScore/AddressScore/AddressScore.tsx similarity index 89% rename from client/src/components/Settings/SettingsAddressScore/SettingsAddressScore.tsx rename to client/src/components/Home/HomeGridUQScore/AddressScore/AddressScore.tsx index ca9ef29e18..654d18ca73 100644 --- a/client/src/components/Settings/SettingsAddressScore/SettingsAddressScore.tsx +++ b/client/src/components/Home/HomeGridUQScore/AddressScore/AddressScore.tsx @@ -10,10 +10,10 @@ import Svg from 'components/ui/Svg'; import { checkMark } from 'svg/misc'; import truncateEthAddress from 'utils/truncateEthAddress'; -import styles from './SettingsAddressScore.module.scss'; -import SettingsAddressScoreProps from './types'; +import styles from './AddressScore.module.scss'; +import AddressScoreProps from './types'; -const SettingsAddressScore: FC = ({ +const AddressScore: FC = ({ address, badge, score, @@ -26,7 +26,9 @@ const SettingsAddressScore: FC = ({ mode, showActiveDot, }) => { - const { t } = useTranslation('translation', { keyPrefix: 'views.settings' }); + const { t } = useTranslation('translation', { + keyPrefix: 'components.home.homeGridUQScore.addressScore', + }); const { address: activeAddress } = useAccount(); const isActive = activeAddress === address; @@ -92,4 +94,4 @@ const SettingsAddressScore: FC = ({ ); }; -export default memo(SettingsAddressScore); +export default memo(AddressScore); diff --git a/client/src/components/Settings/SettingsProgressPath/index.tsx b/client/src/components/Home/HomeGridUQScore/AddressScore/index.tsx similarity index 50% rename from client/src/components/Settings/SettingsProgressPath/index.tsx rename to client/src/components/Home/HomeGridUQScore/AddressScore/index.tsx index 0fb564a413..772b0e39ed 100644 --- a/client/src/components/Settings/SettingsProgressPath/index.tsx +++ b/client/src/components/Home/HomeGridUQScore/AddressScore/index.tsx @@ -1,2 +1,2 @@ // eslint-disable-next-line no-restricted-exports -export { default } from './SettingsProgressPath'; +export { default } from './AddressScore'; diff --git a/client/src/components/Settings/SettingsAddressScore/types.ts b/client/src/components/Home/HomeGridUQScore/AddressScore/types.ts similarity index 85% rename from client/src/components/Settings/SettingsAddressScore/types.ts rename to client/src/components/Home/HomeGridUQScore/AddressScore/types.ts index dfaace9d8b..807f77fa20 100644 --- a/client/src/components/Settings/SettingsAddressScore/types.ts +++ b/client/src/components/Home/HomeGridUQScore/AddressScore/types.ts @@ -1,4 +1,4 @@ -export default interface SettingsAddressScoreProps { +export default interface AddressScoreProps { address: string; areBottomCornersRounded?: boolean; badge: 'primary' | 'secondary'; diff --git a/client/src/components/Home/HomeGridUQScore/HomeGridUQScore.module.scss b/client/src/components/Home/HomeGridUQScore/HomeGridUQScore.module.scss new file mode 100644 index 0000000000..edb1b45dc6 --- /dev/null +++ b/client/src/components/Home/HomeGridUQScore/HomeGridUQScore.module.scss @@ -0,0 +1,35 @@ +.root { + padding: 0 2.4rem 2.4rem; + + .visitDashboard { + height: 4.8rem; + justify-content: left; + font-size: 1rem; + border-bottom: 0.1rem solid $color-octant-grey3; + margin-bottom: 2.4rem; + padding: 0; + } + + .buttonsWrapper { + width: 100%; + display: flex; + justify-content: space-between; + + .button { + cursor: pointer; + flex: 1; + + &:first-child { + margin-right: 1.2rem; + } + } + } +} + +.titleSuffix { + margin-left: auto; + cursor: pointer; + color: $color-octant-green; + font-size: $font-size-10; + font-weight: $font-weight-bold; +} diff --git a/client/src/components/Settings/SettingsUniquenessScoreBox/SettingsUniquenessScoreBox.tsx b/client/src/components/Home/HomeGridUQScore/HomeGridUQScore.tsx similarity index 73% rename from client/src/components/Settings/SettingsUniquenessScoreBox/SettingsUniquenessScoreBox.tsx rename to client/src/components/Home/HomeGridUQScore/HomeGridUQScore.tsx index 61118f8b5f..e05a0860be 100644 --- a/client/src/components/Settings/SettingsUniquenessScoreBox/SettingsUniquenessScoreBox.tsx +++ b/client/src/components/Home/HomeGridUQScore/HomeGridUQScore.tsx @@ -1,15 +1,11 @@ import { watchAccount } from '@wagmi/core'; import uniq from 'lodash/uniq'; -import React, { ReactNode, useEffect, useMemo, useState } from 'react'; +import React, { FC, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useAccount, useConnectors } from 'wagmi'; import { wagmiConfig } from 'api/clients/client-wagmi'; -import ModalSettingsCalculatingUQScore from 'components/Settings/ModalSettingsCalculatingUQScore'; -import ModalSettingsCalculatingYourUniqueness from 'components/Settings/ModalSettingsCalculatingYourUniqueness'; -import ModalSettingsRecalculatingScore from 'components/Settings/ModalSettingsRecalculatingScore'; -import SettingsUniquenessScoreAddresses from 'components/Settings/SettingsUniquenessScoreAddresses'; -import BoxRounded from 'components/ui/BoxRounded'; +import GridTile from 'components/shared/Grid/GridTile'; import Button from 'components/ui/Button'; import { DELEGATION_MIN_SCORE } from 'constants/delegation'; import { GITCOIN_PASSPORT_CUSTOM_OCTANT_DASHBOARD } from 'constants/urls'; @@ -20,12 +16,19 @@ import useCurrentEpoch from 'hooks/queries/useCurrentEpoch'; import useUqScore from 'hooks/queries/useUqScore'; import useUserTOS from 'hooks/queries/useUserTOS'; import toastService from 'services/toastService'; -import useSettingsStore from 'store/settings/store'; +import useDelegationStore from 'store/delegation/store'; -import styles from './SettingsUniquenessScoreBox.module.scss'; +import styles from './HomeGridUQScore.module.scss'; +import HomeGridUQScoreAddresses from './HomeGridUQScoreAddresses'; +import ModalCalculatingUQScore from './ModalCalculatingUQScore'; +import ModalCalculatingYourUniqueness from './ModalCalculatingYourUniqueness'; +import ModalRecalculatingScore from './ModalRecalculatingScore'; +import HomeGridUQScoreProps from './types'; -const SettingsUniquenessScoreBox = (): ReactNode => { - const { t } = useTranslation('translation', { keyPrefix: 'views.settings' }); +const HomeGridUQScore: FC = ({ className }) => { + const { t } = useTranslation('translation', { + keyPrefix: 'components.home.homeGridUQScore', + }); const { address } = useAccount(); const connectors = useConnectors(); const { data: currentEpoch } = useCurrentEpoch(); @@ -52,7 +55,7 @@ const SettingsUniquenessScoreBox = (): ReactNode => { setDelegationSecondaryAddress, setIsDelegationCalculatingUQScoreModalOpen, setIsDelegationCompleted, - } = useSettingsStore(state => ({ + } = useDelegationStore(state => ({ delegationPrimaryAddress: state.data.delegationPrimaryAddress, delegationSecondaryAddress: state.data.delegationSecondaryAddress, isDelegationCalculatingUQScoreModalOpen: state.data.isDelegationCalculatingUQScoreModalOpen, @@ -111,7 +114,7 @@ const SettingsUniquenessScoreBox = (): ReactNode => { }, ); - const modalSettingsCalculatingUQScoreProps = useMemo(() => { + const modalCalculatingUQScoreProps = useMemo(() => { return { isOpen: isDelegationCalculatingUQScoreModalOpen, onClosePanel: () => setIsDelegationCalculatingUQScoreModalOpen(false), @@ -119,7 +122,7 @@ const SettingsUniquenessScoreBox = (): ReactNode => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [isDelegationCalculatingUQScoreModalOpen]); - const modalSettingsRecalculatingScoreProps = useMemo(() => { + const modalRecalculatingScoreProps = useMemo(() => { return { isOpen: isRecalculatingScoreModalOpen, onClosePanel: () => setIsRecalculatingScoreModalOpen(false), @@ -127,7 +130,7 @@ const SettingsUniquenessScoreBox = (): ReactNode => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [isRecalculatingScoreModalOpen]); - const modalSettingsCalculatingYourUniquenessProps = useMemo(() => { + const modalCalculatingYourUniquenessProps = useMemo(() => { return { isOpen: isCalculatingYourUniquenessModalOpen, onClosePanel: () => setIsCalculatingYourUniquenessModalOpen(false), @@ -231,63 +234,57 @@ const SettingsUniquenessScoreBox = (): ReactNode => { }, [isUserTOSAccepted, address]); return ( - setIsCalculatingYourUniquenessModalOpen(true)} - > - {t('whatIsThis')} -
- } - > - <> - - + {t('whatIsThis')} +
+ } + > +
+ + className={styles.visitDashboard} + href={GITCOIN_PASSPORT_CUSTOM_OCTANT_DASHBOARD} + label={t('scoreTooLow')} + variant="link" + /> +
+ + +
- - - - - + + + + + ); }; -export default SettingsUniquenessScoreBox; +export default HomeGridUQScore; diff --git a/client/src/components/Settings/SettingsUniquenessScoreAddresses/SettingsUniquenessScoreAddresses.module.scss b/client/src/components/Home/HomeGridUQScore/HomeGridUQScoreAddresses/HomeGridUQScoreAddresses.module.scss similarity index 94% rename from client/src/components/Settings/SettingsUniquenessScoreAddresses/SettingsUniquenessScoreAddresses.module.scss rename to client/src/components/Home/HomeGridUQScore/HomeGridUQScoreAddresses/HomeGridUQScoreAddresses.module.scss index 05ba83fcad..56e8cea8a7 100644 --- a/client/src/components/Settings/SettingsUniquenessScoreAddresses/SettingsUniquenessScoreAddresses.module.scss +++ b/client/src/components/Home/HomeGridUQScore/HomeGridUQScoreAddresses/HomeGridUQScoreAddresses.module.scss @@ -1,12 +1,11 @@ .root { width: 100%; - height: 7.2rem; + height: 10.4rem; padding: 0 2.4rem; display: flex; align-items: center; border-radius: $border-radius-16; background-color: $color-octant-grey3; - margin: 1.6rem 0 1.4rem; .avatarsGroup { position: relative; @@ -51,11 +50,12 @@ font-weight: $font-weight-bold; letter-spacing: 0.03rem; text-transform: uppercase; + text-align: left; } } .score { - font-size: $font-size-24; + font-size: $font-size-32; font-weight: $font-weight-bold; color: $color-octant-dark; margin-left: auto; diff --git a/client/src/components/Settings/SettingsUniquenessScoreAddresses/SettingsUniquenessScoreAddresses.tsx b/client/src/components/Home/HomeGridUQScore/HomeGridUQScoreAddresses/HomeGridUQScoreAddresses.tsx similarity index 89% rename from client/src/components/Settings/SettingsUniquenessScoreAddresses/SettingsUniquenessScoreAddresses.tsx rename to client/src/components/Home/HomeGridUQScore/HomeGridUQScoreAddresses/HomeGridUQScoreAddresses.tsx index 5c9ee98235..f7a65fb293 100644 --- a/client/src/components/Settings/SettingsUniquenessScoreAddresses/SettingsUniquenessScoreAddresses.tsx +++ b/client/src/components/Home/HomeGridUQScore/HomeGridUQScoreAddresses/HomeGridUQScoreAddresses.tsx @@ -6,16 +6,14 @@ import { useAccount } from 'wagmi'; import Identicon from 'components/ui/Identicon'; import Svg from 'components/ui/Svg'; -import useSettingsStore from 'store/settings/store'; +import useDelegationStore from 'store/delegation/store'; import { octant } from 'svg/logo'; import truncateEthAddress from 'utils/truncateEthAddress'; -import styles from './SettingsUniquenessScoreAddresses.module.scss'; -import SettingsUniquenessScoreAddressesProps from './types'; +import styles from './HomeGridUQScoreAddresses.module.scss'; +import HomeGridUQScoreAddressesProps from './types'; -const SettingsUniquenessScoreAddresses: FC = ({ - isFetchingScore, -}) => { +const HomeGridUQScoreAddresses: FC = ({ isFetchingScore }) => { const { t } = useTranslation('translation', { keyPrefix: 'views.settings' }); const ref = useRef(null); @@ -27,7 +25,7 @@ const SettingsUniquenessScoreAddresses: FC ({ + } = useDelegationStore(state => ({ delegationPrimaryAddress: state.data.delegationPrimaryAddress, delegationSecondaryAddress: state.data.delegationSecondaryAddress, isDelegationCompleted: state.data.isDelegationCompleted, @@ -102,4 +100,4 @@ const SettingsUniquenessScoreAddresses: FC = ({ - setShowCloseButton, -}) => { - const { t } = useTranslation('translation', { keyPrefix: 'views.settings' }); +import AddressScore from '../../AddressScore'; +import ProgressPath from '../../ProgressPath'; + +const CalculatingUQScore: FC = ({ setShowCloseButton }) => { + const { t } = useTranslation('translation', { + keyPrefix: 'components.home.homeGridUQScore.modalCalculatingUQScore', + }); const { address } = useAccount(); const { data: isUserTOSAccepted } = useUserTOS(); @@ -41,7 +42,7 @@ const SettingsCalculatingUQScore: FC = ({ setCalculatingUQScoreMode, setIsDelegationCompleted, setSecondaryAddressScore, - } = useSettingsStore(state => ({ + } = useDelegationStore(state => ({ calculatingUQScoreMode: state.data.calculatingUQScoreMode, delegationPrimaryAddress: state.data.delegationPrimaryAddress, delegationSecondaryAddress: state.data.delegationSecondaryAddress, @@ -160,7 +161,7 @@ const SettingsCalculatingUQScore: FC = ({ return ( - = ({ score={primaryAddressScore ?? 0} showActiveDot={calculatingUQScoreMode === 'sign'} /> - = ({ showActiveDot={calculatingUQScoreMode === 'sign'} /> {calculatingUQScoreMode === 'score' && ( - + )} {showLowScoreInfo && ( = ({ ); }; -export default SettingsCalculatingUQScore; +export default CalculatingUQScore; diff --git a/client/src/components/Settings/SettingsAddressScore/index.tsx b/client/src/components/Home/HomeGridUQScore/ModalCalculatingUQScore/CalculatingUQScore/index.tsx similarity index 50% rename from client/src/components/Settings/SettingsAddressScore/index.tsx rename to client/src/components/Home/HomeGridUQScore/ModalCalculatingUQScore/CalculatingUQScore/index.tsx index ab04a4525c..7a7fa10116 100644 --- a/client/src/components/Settings/SettingsAddressScore/index.tsx +++ b/client/src/components/Home/HomeGridUQScore/ModalCalculatingUQScore/CalculatingUQScore/index.tsx @@ -1,2 +1,2 @@ // eslint-disable-next-line no-restricted-exports -export { default } from './SettingsAddressScore'; +export { default } from './CalculatingUQScore'; diff --git a/client/src/components/Settings/SettingsCalculatingUQScore/types.ts b/client/src/components/Home/HomeGridUQScore/ModalCalculatingUQScore/CalculatingUQScore/types.ts similarity index 100% rename from client/src/components/Settings/SettingsCalculatingUQScore/types.ts rename to client/src/components/Home/HomeGridUQScore/ModalCalculatingUQScore/CalculatingUQScore/types.ts diff --git a/client/src/components/Settings/ModalSettingsCalculatingUQScore/ModalSettingsCalculatingUQScore.module.scss b/client/src/components/Home/HomeGridUQScore/ModalCalculatingUQScore/ModalCalculatingUQScore.module.scss similarity index 100% rename from client/src/components/Settings/ModalSettingsCalculatingUQScore/ModalSettingsCalculatingUQScore.module.scss rename to client/src/components/Home/HomeGridUQScore/ModalCalculatingUQScore/ModalCalculatingUQScore.module.scss diff --git a/client/src/components/Settings/ModalSettingsCalculatingUQScore/ModalSettingsCalculatingUQScore.tsx b/client/src/components/Home/HomeGridUQScore/ModalCalculatingUQScore/ModalCalculatingUQScore.tsx similarity index 61% rename from client/src/components/Settings/ModalSettingsCalculatingUQScore/ModalSettingsCalculatingUQScore.tsx rename to client/src/components/Home/HomeGridUQScore/ModalCalculatingUQScore/ModalCalculatingUQScore.tsx index a2048c5968..50422fc0e3 100644 --- a/client/src/components/Settings/ModalSettingsCalculatingUQScore/ModalSettingsCalculatingUQScore.tsx +++ b/client/src/components/Home/HomeGridUQScore/ModalCalculatingUQScore/ModalCalculatingUQScore.tsx @@ -1,20 +1,20 @@ import React, { FC, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import SettingsCalculatingUQScore from 'components/Settings/SettingsCalculatingUQScore'; import Button from 'components/ui/Button'; import Modal from 'components/ui/Modal'; -import useSettingsStore from 'store/settings/store'; +import useDelegationStore from 'store/delegation/store'; -import styles from './ModalSettingsCalculatingUQScore.module.scss'; -import ModalSettingsCalculatingUQScoreProps from './types'; +import CalculatingUQScore from './CalculatingUQScore'; +import styles from './ModalCalculatingUQScore.module.scss'; +import ModalCalculatingUQScoreProps from './types'; -const ModalSettingsCalculatingUQScore: FC = ({ - modalProps, -}) => { - const { t } = useTranslation('translation', { keyPrefix: 'views.settings' }); +const ModalCalculatingUQScore: FC = ({ modalProps }) => { + const { t } = useTranslation('translation', { + keyPrefix: 'components.home.homeGridUQScore.modalCalculatingUQScore', + }); - const { calculatingUQScoreMode, setIsDelegationConnectModalOpen } = useSettingsStore(state => ({ + const { calculatingUQScoreMode, setIsDelegationConnectModalOpen } = useDelegationStore(state => ({ calculatingUQScoreMode: state.data.calculatingUQScoreMode, setIsDelegationConnectModalOpen: state.setIsDelegationConnectModalOpen, })); @@ -24,7 +24,7 @@ const ModalSettingsCalculatingUQScore: FC return ( @@ -45,9 +45,9 @@ const ModalSettingsCalculatingUQScore: FC showCloseButton={showCloseButton} {...modalProps} > - + ); }; -export default ModalSettingsCalculatingUQScore; +export default ModalCalculatingUQScore; diff --git a/client/src/components/Home/HomeGridUQScore/ModalCalculatingUQScore/index.tsx b/client/src/components/Home/HomeGridUQScore/ModalCalculatingUQScore/index.tsx new file mode 100644 index 0000000000..1ecc801c97 --- /dev/null +++ b/client/src/components/Home/HomeGridUQScore/ModalCalculatingUQScore/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './ModalCalculatingUQScore'; diff --git a/client/src/components/Settings/ModalSettingsCalculatingUQScore/types.ts b/client/src/components/Home/HomeGridUQScore/ModalCalculatingUQScore/types.ts similarity index 100% rename from client/src/components/Settings/ModalSettingsCalculatingUQScore/types.ts rename to client/src/components/Home/HomeGridUQScore/ModalCalculatingUQScore/types.ts diff --git a/client/src/components/Settings/ModalSettingsCalculatingYourUniqueness/ModalSettingsCalculatingYourUniqueness.module.scss b/client/src/components/Home/HomeGridUQScore/ModalCalculatingYourUniqueness/ModalCalculatingYourUniqueness.module.scss similarity index 100% rename from client/src/components/Settings/ModalSettingsCalculatingYourUniqueness/ModalSettingsCalculatingYourUniqueness.module.scss rename to client/src/components/Home/HomeGridUQScore/ModalCalculatingYourUniqueness/ModalCalculatingYourUniqueness.module.scss diff --git a/client/src/components/Settings/ModalSettingsCalculatingYourUniqueness/ModalSettingsCalculatingYourUniqueness.tsx b/client/src/components/Home/HomeGridUQScore/ModalCalculatingYourUniqueness/ModalCalculatingYourUniqueness.tsx similarity index 73% rename from client/src/components/Settings/ModalSettingsCalculatingYourUniqueness/ModalSettingsCalculatingYourUniqueness.tsx rename to client/src/components/Home/HomeGridUQScore/ModalCalculatingYourUniqueness/ModalCalculatingYourUniqueness.tsx index 58a900748b..95ad8f9a00 100644 --- a/client/src/components/Settings/ModalSettingsCalculatingYourUniqueness/ModalSettingsCalculatingYourUniqueness.tsx +++ b/client/src/components/Home/HomeGridUQScore/ModalCalculatingYourUniqueness/ModalCalculatingYourUniqueness.tsx @@ -13,18 +13,21 @@ import { } from 'constants/urls'; import useModalStepperNavigation from 'hooks/helpers/useModalStepperNavigation'; -import styles from './ModalSettingsCalculatingYourUniqueness.module.scss'; -import ModalSettingsCalculatingYourUniquenessProps from './types'; +import styles from './ModalCalculatingYourUniqueness.module.scss'; +import ModalCalculatingYourUniquenessProps from './types'; -const ModalSettingsCalculatingYourUniqueness: FC = ({ +const ModalCalculatingYourUniqueness: FC = ({ modalProps, }) => { - const { t } = useTranslation('translation', { keyPrefix: 'views.settings' }); + const translationKeyPrefix = 'components.home.homeGridUQScore.modalCalculatingYourUniqueness'; + const { t } = useTranslation('translation', { + keyPrefix: translationKeyPrefix, + }); const steps = [ ]} - i18nKey="views.settings.calculatingYourUniquenessStep1" + i18nKey={`${translationKeyPrefix}.calculatingYourUniquenessStep1`} />, , ]} - i18nKey="views.settings.calculatingYourUniquenessStep2" + i18nKey={`${translationKeyPrefix}.calculatingYourUniquenessStep2`} />, , ]} - i18nKey="views.settings.calculatingYourUniquenessStep3" + i18nKey={`${translationKeyPrefix}.calculatingYourUniquenessStep3`} />, ]; @@ -55,7 +58,7 @@ const ModalSettingsCalculatingYourUniqueness: FC @@ -75,7 +78,7 @@ const ModalSettingsCalculatingYourUniqueness: FC { if (stepIndex === currentStepIndex && stepIndex !== steps.length - 1) { @@ -89,4 +92,4 @@ const ModalSettingsCalculatingYourUniqueness: FC; } diff --git a/client/src/components/Settings/ModalSettingsRecalculatingScore/ModalSettingsRecalculatingScore.module.scss b/client/src/components/Home/HomeGridUQScore/ModalRecalculatingScore/ModalRecalculatingScore.module.scss similarity index 100% rename from client/src/components/Settings/ModalSettingsRecalculatingScore/ModalSettingsRecalculatingScore.module.scss rename to client/src/components/Home/HomeGridUQScore/ModalRecalculatingScore/ModalRecalculatingScore.module.scss diff --git a/client/src/components/Home/HomeGridUQScore/ModalRecalculatingScore/ModalRecalculatingScore.tsx b/client/src/components/Home/HomeGridUQScore/ModalRecalculatingScore/ModalRecalculatingScore.tsx new file mode 100644 index 0000000000..1b127b1e75 --- /dev/null +++ b/client/src/components/Home/HomeGridUQScore/ModalRecalculatingScore/ModalRecalculatingScore.tsx @@ -0,0 +1,27 @@ +import React, { FC, memo } from 'react'; +import { useTranslation } from 'react-i18next'; + +import Modal from 'components/ui/Modal'; + +import styles from './ModalRecalculatingScore.module.scss'; +import RecalculatingScore from './RecalculatingScore'; +import ModalRecalculatingScoreProps from './types'; + +const ModalRecalculatingScore: FC = ({ modalProps }) => { + const { t } = useTranslation('translation', { + keyPrefix: 'components.home.homeGridUQScore.modalRecalculatingScore', + }); + + return ( + + + + ); +}; + +export default memo(ModalRecalculatingScore); diff --git a/client/src/components/Settings/SettingsRecalculatingScore/SettingsRecalculatingScore.tsx b/client/src/components/Home/HomeGridUQScore/ModalRecalculatingScore/RecalculatingScore/RecalculatingScore.tsx similarity index 87% rename from client/src/components/Settings/SettingsRecalculatingScore/SettingsRecalculatingScore.tsx rename to client/src/components/Home/HomeGridUQScore/ModalRecalculatingScore/RecalculatingScore/RecalculatingScore.tsx index e09a3339ff..eb230d4110 100644 --- a/client/src/components/Settings/SettingsRecalculatingScore/SettingsRecalculatingScore.tsx +++ b/client/src/components/Home/HomeGridUQScore/ModalRecalculatingScore/RecalculatingScore/RecalculatingScore.tsx @@ -1,19 +1,20 @@ import React, { FC, useEffect, useMemo, useState } from 'react'; import { useAccount } from 'wagmi'; -import SettingsAddressScore from 'components/Settings/SettingsAddressScore'; -import SettingsProgressPath from 'components/Settings/SettingsProgressPath'; import { DELEGATION_MIN_SCORE } from 'constants/delegation'; import useRefreshAntisybilStatus from 'hooks/mutations/useRefreshAntisybilStatus'; import useAntisybilStatusScore from 'hooks/queries/useAntisybilStatusScore'; import useCurrentEpoch from 'hooks/queries/useCurrentEpoch'; import useUqScore from 'hooks/queries/useUqScore'; import useUserTOS from 'hooks/queries/useUserTOS'; -import useSettingsStore from 'store/settings/store'; +import useDelegationStore from 'store/delegation/store'; -import SettingsRecalculatingScoreProps from './types'; +import RecalculatingScoreProps from './types'; -const SettingsRecalculatingScore: FC = ({ onLastStepDone }) => { +import AddressScore from '../../AddressScore'; +import ProgressPath from '../../ProgressPath'; + +const RecalculatingScore: FC = ({ onLastStepDone }) => { const { data: currentEpoch } = useCurrentEpoch(); const { address } = useAccount(); const { data: isUserTOSAccepted } = useUserTOS(); @@ -25,7 +26,7 @@ const SettingsRecalculatingScore: FC = ({ onLas setSecondaryAddressScore, isDelegationCompleted, delegationSecondaryAddress, - } = useSettingsStore(state => ({ + } = useDelegationStore(state => ({ delegationSecondaryAddress: state.data.delegationSecondaryAddress, isDelegationCompleted: state.data.isDelegationCompleted, setPrimaryAddressScore: state.setPrimaryAddressScore, @@ -110,7 +111,7 @@ const SettingsRecalculatingScore: FC = ({ onLas return ( <> - = ({ onLas score={calculatedUqScore} scoreHighlight={scoreHighlight} /> - + ); }; -export default SettingsRecalculatingScore; +export default RecalculatingScore; diff --git a/client/src/components/Home/HomeGridUQScore/ModalRecalculatingScore/RecalculatingScore/index.tsx b/client/src/components/Home/HomeGridUQScore/ModalRecalculatingScore/RecalculatingScore/index.tsx new file mode 100644 index 0000000000..05b1f8c13d --- /dev/null +++ b/client/src/components/Home/HomeGridUQScore/ModalRecalculatingScore/RecalculatingScore/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './RecalculatingScore'; diff --git a/client/src/components/Home/HomeGridUQScore/ModalRecalculatingScore/RecalculatingScore/types.ts b/client/src/components/Home/HomeGridUQScore/ModalRecalculatingScore/RecalculatingScore/types.ts new file mode 100644 index 0000000000..64f5c89c10 --- /dev/null +++ b/client/src/components/Home/HomeGridUQScore/ModalRecalculatingScore/RecalculatingScore/types.ts @@ -0,0 +1,3 @@ +export default interface RecalculatingScoreProps { + onLastStepDone: () => void; +} diff --git a/client/src/components/Home/HomeGridUQScore/ModalRecalculatingScore/index.tsx b/client/src/components/Home/HomeGridUQScore/ModalRecalculatingScore/index.tsx new file mode 100644 index 0000000000..b5db14a56d --- /dev/null +++ b/client/src/components/Home/HomeGridUQScore/ModalRecalculatingScore/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './ModalRecalculatingScore'; diff --git a/client/src/components/Settings/ModalSettingsCalculatingYourUniqueness/types.ts b/client/src/components/Home/HomeGridUQScore/ModalRecalculatingScore/types.ts similarity index 60% rename from client/src/components/Settings/ModalSettingsCalculatingYourUniqueness/types.ts rename to client/src/components/Home/HomeGridUQScore/ModalRecalculatingScore/types.ts index 04c67e7364..7615d40575 100644 --- a/client/src/components/Settings/ModalSettingsCalculatingYourUniqueness/types.ts +++ b/client/src/components/Home/HomeGridUQScore/ModalRecalculatingScore/types.ts @@ -1,5 +1,5 @@ import ModalProps from 'components/ui/Modal/types'; -export default interface ModalSettingsCalculatingYourUniquenessProps { +export default interface ModalRecalculatingScoreProps { modalProps: Omit; } diff --git a/client/src/components/Settings/SettingsProgressPath/SettingsProgressPath.module.scss b/client/src/components/Home/HomeGridUQScore/ProgressPath/ProgressPath.module.scss similarity index 100% rename from client/src/components/Settings/SettingsProgressPath/SettingsProgressPath.module.scss rename to client/src/components/Home/HomeGridUQScore/ProgressPath/ProgressPath.module.scss diff --git a/client/src/components/Settings/SettingsProgressPath/SettingsProgressPath.tsx b/client/src/components/Home/HomeGridUQScore/ProgressPath/ProgressPath.tsx similarity index 84% rename from client/src/components/Settings/SettingsProgressPath/SettingsProgressPath.tsx rename to client/src/components/Home/HomeGridUQScore/ProgressPath/ProgressPath.tsx index 0dd0001970..85edc6c1d3 100644 --- a/client/src/components/Settings/SettingsProgressPath/SettingsProgressPath.tsx +++ b/client/src/components/Home/HomeGridUQScore/ProgressPath/ProgressPath.tsx @@ -3,11 +3,13 @@ import { motion } from 'framer-motion'; import React, { FC } from 'react'; import { useTranslation } from 'react-i18next'; -import styles from './SettingsProgressPath.module.scss'; -import SettingsProgressPathProps from './types'; +import styles from './ProgressPath.module.scss'; +import ProgressPathProps from './types'; -const SettingsProgressPath: FC = ({ lastDoneStep }) => { - const { t } = useTranslation('translation', { keyPrefix: 'views.settings' }); +const ProgressPath: FC = ({ lastDoneStep }) => { + const { t } = useTranslation('translation', { + keyPrefix: 'components.home.homeGridUQScore.progressPath', + }); const steps = [t('checkingPassportScore'), t('checkingAllowlist'), t('finished')]; return ( @@ -54,4 +56,4 @@ const SettingsProgressPath: FC = ({ lastDoneStep }) = ); }; -export default SettingsProgressPath; +export default ProgressPath; diff --git a/client/src/components/Home/HomeGridUQScore/ProgressPath/index.tsx b/client/src/components/Home/HomeGridUQScore/ProgressPath/index.tsx new file mode 100644 index 0000000000..204ba1a586 --- /dev/null +++ b/client/src/components/Home/HomeGridUQScore/ProgressPath/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './ProgressPath'; diff --git a/client/src/components/Home/HomeGridUQScore/ProgressPath/types.ts b/client/src/components/Home/HomeGridUQScore/ProgressPath/types.ts new file mode 100644 index 0000000000..f4980ed0bc --- /dev/null +++ b/client/src/components/Home/HomeGridUQScore/ProgressPath/types.ts @@ -0,0 +1,3 @@ +export default interface ProgressPathProps { + lastDoneStep: null | 0 | 1 | 2; +} diff --git a/client/src/components/Home/HomeGridUQScore/index.tsx b/client/src/components/Home/HomeGridUQScore/index.tsx new file mode 100644 index 0000000000..c98a2ffb08 --- /dev/null +++ b/client/src/components/Home/HomeGridUQScore/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './HomeGridUQScore'; diff --git a/client/src/components/Home/HomeGridUQScore/types.ts b/client/src/components/Home/HomeGridUQScore/types.ts new file mode 100644 index 0000000000..369ea383ce --- /dev/null +++ b/client/src/components/Home/HomeGridUQScore/types.ts @@ -0,0 +1,3 @@ +export default interface HomeGridUQScoreProps { + className?: string; +} diff --git a/client/src/components/Settings/ModalSettingsCalculatingUQScore/index.tsx b/client/src/components/Settings/ModalSettingsCalculatingUQScore/index.tsx deleted file mode 100644 index 4074153816..0000000000 --- a/client/src/components/Settings/ModalSettingsCalculatingUQScore/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line no-restricted-exports -export { default } from './ModalSettingsCalculatingUQScore'; diff --git a/client/src/components/Settings/ModalSettingsCalculatingYourUniqueness/index.tsx b/client/src/components/Settings/ModalSettingsCalculatingYourUniqueness/index.tsx deleted file mode 100644 index 886be24fa6..0000000000 --- a/client/src/components/Settings/ModalSettingsCalculatingYourUniqueness/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line no-restricted-exports -export { default } from './ModalSettingsCalculatingYourUniqueness'; diff --git a/client/src/components/Settings/ModalSettingsRecalculatingScore/ModalSettingsRecalculatingScore.tsx b/client/src/components/Settings/ModalSettingsRecalculatingScore/ModalSettingsRecalculatingScore.tsx deleted file mode 100644 index c060abf828..0000000000 --- a/client/src/components/Settings/ModalSettingsRecalculatingScore/ModalSettingsRecalculatingScore.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import React, { FC, memo } from 'react'; -import { useTranslation } from 'react-i18next'; - -import SettingsRecalculatingScore from 'components/Settings/SettingsRecalculatingScore'; -import Modal from 'components/ui/Modal'; - -import styles from './ModalSettingsRecalculatingScore.module.scss'; -import ModalSettingsRecalculatingScoreProps from './types'; - -const ModalSettingsRecalculatingScore: FC = ({ - modalProps, -}) => { - const { t } = useTranslation('translation', { keyPrefix: 'views.settings' }); - - return ( - - - - ); -}; - -export default memo(ModalSettingsRecalculatingScore); diff --git a/client/src/components/Settings/ModalSettingsRecalculatingScore/index.tsx b/client/src/components/Settings/ModalSettingsRecalculatingScore/index.tsx deleted file mode 100644 index 4d7a5e86f3..0000000000 --- a/client/src/components/Settings/ModalSettingsRecalculatingScore/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line no-restricted-exports -export { default } from './ModalSettingsRecalculatingScore'; diff --git a/client/src/components/Settings/SettingsCalculatingUQScore/index.tsx b/client/src/components/Settings/SettingsCalculatingUQScore/index.tsx deleted file mode 100644 index e96f88b63f..0000000000 --- a/client/src/components/Settings/SettingsCalculatingUQScore/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line no-restricted-exports -export { default } from './SettingsCalculatingUQScore'; diff --git a/client/src/components/Settings/SettingsProgressPath/types.ts b/client/src/components/Settings/SettingsProgressPath/types.ts deleted file mode 100644 index 1ebf924e5c..0000000000 --- a/client/src/components/Settings/SettingsProgressPath/types.ts +++ /dev/null @@ -1,3 +0,0 @@ -export default interface SettingsProgressPathProps { - lastDoneStep: null | 0 | 1 | 2; -} diff --git a/client/src/components/Settings/SettingsRecalculatingScore/index.tsx b/client/src/components/Settings/SettingsRecalculatingScore/index.tsx deleted file mode 100644 index d59eefb504..0000000000 --- a/client/src/components/Settings/SettingsRecalculatingScore/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line no-restricted-exports -export { default } from './SettingsRecalculatingScore'; diff --git a/client/src/components/Settings/SettingsRecalculatingScore/types.ts b/client/src/components/Settings/SettingsRecalculatingScore/types.ts deleted file mode 100644 index 6d6f87ef88..0000000000 --- a/client/src/components/Settings/SettingsRecalculatingScore/types.ts +++ /dev/null @@ -1,3 +0,0 @@ -export default interface SettingsRecalculatingScoreProps { - onLastStepDone: () => void; -} diff --git a/client/src/components/Settings/SettingsUniquenessScoreAddresses/index.tsx b/client/src/components/Settings/SettingsUniquenessScoreAddresses/index.tsx deleted file mode 100644 index 750c5b4fbf..0000000000 --- a/client/src/components/Settings/SettingsUniquenessScoreAddresses/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line no-restricted-exports -export { default } from './SettingsUniquenessScoreAddresses'; diff --git a/client/src/components/Settings/SettingsUniquenessScoreAddresses/types.ts b/client/src/components/Settings/SettingsUniquenessScoreAddresses/types.ts deleted file mode 100644 index 0fc57c88bc..0000000000 --- a/client/src/components/Settings/SettingsUniquenessScoreAddresses/types.ts +++ /dev/null @@ -1,3 +0,0 @@ -export default interface SettingsUniquenessScoreAddressesProps { - isFetchingScore?: boolean; -} diff --git a/client/src/components/Settings/SettingsUniquenessScoreBox/SettingsUniquenessScoreBox.module.scss b/client/src/components/Settings/SettingsUniquenessScoreBox/SettingsUniquenessScoreBox.module.scss deleted file mode 100644 index c5e2a130d8..0000000000 --- a/client/src/components/Settings/SettingsUniquenessScoreBox/SettingsUniquenessScoreBox.module.scss +++ /dev/null @@ -1,38 +0,0 @@ -.root { - padding: 4.4rem 2.2rem 0.5rem; - height: 26.4rem; - - @media #{$desktop-up} { - padding: 4.5rem 2.2rem 2.5rem; - } - - .titleSuffix { - cursor: pointer; - color: $color-octant-green; - font-size: $font-size-10; - font-weight: $font-weight-bold; - } - - .visitDashboard { - font-size: 1rem; - padding: 0 0 0.8rem 0; - margin: 0 0 0.8rem 0; - border-bottom: 0.1rem solid $color-octant-grey3; - } - - .buttonsWrapper { - margin-bottom: 1.6rem; - width: 100%; - display: flex; - justify-content: space-between; - - .button { - cursor: pointer; - flex: 1; - - &:first-child { - margin-right: 1.2rem; - } - } - } -} diff --git a/client/src/components/Settings/SettingsUniquenessScoreBox/index.tsx b/client/src/components/Settings/SettingsUniquenessScoreBox/index.tsx deleted file mode 100644 index 26449d2f5a..0000000000 --- a/client/src/components/Settings/SettingsUniquenessScoreBox/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line no-restricted-exports -export { default } from './SettingsUniquenessScoreBox'; diff --git a/client/src/constants/localStorageKeys.ts b/client/src/constants/localStorageKeys.ts index b8b4d606ef..d658ce6797 100644 --- a/client/src/constants/localStorageKeys.ts +++ b/client/src/constants/localStorageKeys.ts @@ -2,6 +2,7 @@ const SPACER = '_'; const getLocalStorageKey = (prefix: string, suffix: string): string => `${prefix}${SPACER}${suffix}`; +// Allocation const allocationPrefix = 'allocation'; export const ALLOCATION_ITEMS_KEY = getLocalStorageKey(allocationPrefix, 'items'); export const ALLOCATION_REWARDS_FOR_PROJECTS = getLocalStorageKey( @@ -9,12 +10,14 @@ export const ALLOCATION_REWARDS_FOR_PROJECTS = getLocalStorageKey( 'rewardsForProjects', ); +// Projects const projectsPrefix = 'projects'; export const PROJECTS_ADDRESSES_RANDOMIZED_ORDER = getLocalStorageKey( projectsPrefix, 'addressesRandomizedOrder', ); +// Onboarding const onboardingPrefix = 'onboarding'; export const IS_ONBOARDING_DONE = getLocalStorageKey(onboardingPrefix, 'isOnboardingDone'); export const HAS_ONBOARDING_BEEN_CLOSED = getLocalStorageKey( @@ -23,6 +26,7 @@ export const HAS_ONBOARDING_BEEN_CLOSED = getLocalStorageKey( ); export const LAST_SEEN_STEP = getLocalStorageKey(onboardingPrefix, 'lastSeenStep'); +// Settings const settingsPrefix = 'settings'; export const IS_ONBOARDING_ALWAYS_VISIBLE = getLocalStorageKey( settingsPrefix, @@ -40,27 +44,36 @@ export const IS_CRYPTO_MAIN_VALUE_DISPLAY = getLocalStorageKey( 'isCryptoMainValueDisplay', ); +// Delegation +const delegationPrefix = 'delegation'; export const IS_DELEGATION_IN_PROGRESS = getLocalStorageKey( - settingsPrefix, + delegationPrefix, 'isDelegationInProgress', ); -export const IS_DELEGATION_COMPLETED = getLocalStorageKey(settingsPrefix, 'isDelegationCompleted'); +export const IS_DELEGATION_COMPLETED = getLocalStorageKey( + delegationPrefix, + 'isDelegationCompleted', +); export const DELEGATION_PRIMARY_ADDRESS = getLocalStorageKey( - settingsPrefix, + delegationPrefix, 'delegationPrimaryAddress', ); export const DELEGATION_SECONDARY_ADDRESS = getLocalStorageKey( - settingsPrefix, + delegationPrefix, 'delegationSecondaryAddress', ); -export const PRIMARY_ADDRESS_SCORE = getLocalStorageKey(settingsPrefix, 'primaryAddressScore'); +export const PRIMARY_ADDRESS_SCORE = getLocalStorageKey(delegationPrefix, 'primaryAddressScore'); -export const SECONDARY_ADDRESS_SCORE = getLocalStorageKey(settingsPrefix, 'secondaryAddressScore'); +export const SECONDARY_ADDRESS_SCORE = getLocalStorageKey( + delegationPrefix, + 'secondaryAddressScore', +); +// TipTiles const tipTilesPrefix = 'tipTiles'; export const WAS_ADD_FAVOURITES_ALREADY_CLOSED_TIP = getLocalStorageKey( tipTilesPrefix, diff --git a/client/src/locales/en/translation.json b/client/src/locales/en/translation.json index e7902dcc5d..6b924ed620 100644 --- a/client/src/locales/en/translation.json +++ b/client/src/locales/en/translation.json @@ -99,6 +99,39 @@ "estimatedGasPrice": "Est. gas price", "withdrawAll": "Withdraw all" } + }, + "homeGridUQScore": { + "yourUniquenessScore": "Your uniqueness score", + "recalculate": "Recalculate", + "delegate": "Delegate", + "whatIsThis": "What is this?", + "scoreTooLow": "Score too low? Visit the Octant Passport dashboard", + "addresses": "addresses", + "addressScore": { + "signMessage":"Sign message" + }, + "progressPath": { + "checkingPassportScore":"Checking Passport score", + "finished": "Finished", + "checkingAllowlist": "Checking allowlist" + }, + "modalCalculatingYourUniqueness": { + "calculatingYourUniqueness": "Calculating your uniqueness", + "calculatingYourUniquenessStep1": "To prove your uniqueness your need a <0>Gitcoin\u00a0Passport score of 20 or higher.

If you have this your donations will attract the maximum amount of match funding. If not, maximum match funding will be set to 20%.

You can increase your score in a couple of different ways.", + "calculatingYourUniquenessStep2": "You can go to our <0>Passport dashboard and add stamps to the score for your Octant address.

If your Passport score is not on your primary address, you can delegate your score from another address with a 20+ Passport score.

You can only do this once, and it must be done before allocating to benefit the current epoch.", + "calculatingYourUniquenessStep3": "Delegation will not link your addresses or compromise your privacy in any way.

We require proof of uniqueness to defend against sybil attacks as we have switched to a quadratic funding model.

To learn more, check out Gitcoin’s handy guide to <0>scoring 20, for humans." + }, + "modalCalculatingUQScore": { + "switchAccounts": "Switch accounts", + "signMessages": "Sign messages", + "switchAccount": "Switch account", + "calculatingScore":"Calculating score", + "delegationFailedText": "Delegation failed — your score needs to be 20 or higher. Please try another address", + "delegationMessageToSign": "Delegation of UQ score from {{delegationSecondaryAddress}} to {{delegationPrimaryAddress}}" + }, + "modalRecalculatingScore": { + "recalculatingScore":"Recalculating score" + } } }, "settings": { diff --git a/client/src/store/delegation/store.ts b/client/src/store/delegation/store.ts new file mode 100644 index 0000000000..33fe90ad54 --- /dev/null +++ b/client/src/store/delegation/store.ts @@ -0,0 +1,101 @@ +import { + DELEGATION_PRIMARY_ADDRESS, + DELEGATION_SECONDARY_ADDRESS, + IS_DELEGATION_COMPLETED, + PRIMARY_ADDRESS_SCORE, + SECONDARY_ADDRESS_SCORE, +} from 'constants/localStorageKeys'; +import { getStoreWithMeta } from 'store/utils/getStoreWithMeta'; + +import { DelegationData, DelegationMethods } from './types'; + +export const initialState: DelegationData = { + calculatingUQScoreMode: 'score', + delegationPrimaryAddress: undefined, + delegationSecondaryAddress: undefined, + isDelegationCalculatingUQScoreModalOpen: false, + isDelegationCompleted: false, + isDelegationConnectModalOpen: false, + isDelegationInProgress: false, + primaryAddressScore: undefined, + secondaryAddressScore: undefined, +}; + +export default getStoreWithMeta({ + getStoreMethods: set => ({ + reset: () => set({ data: initialState }), + // eslint-disable-next-line @typescript-eslint/naming-convention + setCalculatingUQScoreMode: payload => { + set(state => ({ data: { ...state.data, calculatingUQScoreMode: payload } })); + }, + + // eslint-disable-next-line @typescript-eslint/naming-convention + setDelegationPrimaryAddress: payload => { + localStorage.setItem(DELEGATION_PRIMARY_ADDRESS, JSON.stringify(payload)); + set(state => ({ data: { ...state.data, delegationPrimaryAddress: payload } })); + }, + + // eslint-disable-next-line @typescript-eslint/naming-convention + setDelegationSecondaryAddress: payload => { + localStorage.setItem(DELEGATION_SECONDARY_ADDRESS, JSON.stringify(payload)); + set(state => ({ data: { ...state.data, delegationSecondaryAddress: payload } })); + }, + + // eslint-disable-next-line @typescript-eslint/naming-convention + setIsDelegationCalculatingUQScoreModalOpen: payload => { + set(state => ({ data: { ...state.data, isDelegationCalculatingUQScoreModalOpen: payload } })); + }, + + // eslint-disable-next-line @typescript-eslint/naming-convention + setIsDelegationCompleted: payload => { + localStorage.setItem(IS_DELEGATION_COMPLETED, JSON.stringify(payload)); + set(state => ({ data: { ...state.data, isDelegationCompleted: payload } })); + }, + + // eslint-disable-next-line @typescript-eslint/naming-convention + setIsDelegationConnectModalOpen: payload => { + set(state => ({ data: { ...state.data, isDelegationConnectModalOpen: payload } })); + }, + + // eslint-disable-next-line @typescript-eslint/naming-convention + setIsDelegationInProgress: payload => { + set(state => ({ data: { ...state.data, isDelegationInProgress: payload } })); + }, + + // eslint-disable-next-line @typescript-eslint/naming-convention + setPrimaryAddressScore: payload => { + localStorage.setItem(PRIMARY_ADDRESS_SCORE, JSON.stringify(payload)); + set(state => ({ data: { ...state.data, primaryAddressScore: payload } })); + }, + + // eslint-disable-next-line @typescript-eslint/naming-convention + setSecondaryAddressScore: payload => { + localStorage.setItem(SECONDARY_ADDRESS_SCORE, JSON.stringify(payload)); + set(state => ({ data: { ...state.data, secondaryAddressScore: payload } })); + }, + + setValuesFromLocalStorage: () => + set({ + data: { + ...initialState, + delegationPrimaryAddress: JSON.parse( + localStorage.getItem(DELEGATION_PRIMARY_ADDRESS) || 'null', + ), + delegationSecondaryAddress: JSON.parse( + localStorage.getItem(DELEGATION_SECONDARY_ADDRESS) || 'null', + ), + isDelegationCompleted: JSON.parse( + localStorage.getItem(IS_DELEGATION_COMPLETED) || 'false', + ), + primaryAddressScore: JSON.parse(localStorage.getItem(PRIMARY_ADDRESS_SCORE) || 'null'), + secondaryAddressScore: JSON.parse( + localStorage.getItem(SECONDARY_ADDRESS_SCORE) || 'null', + ), + }, + meta: { + isInitialized: true, + }, + }), + }), + initialState, +}); diff --git a/client/src/store/delegation/types.ts b/client/src/store/delegation/types.ts new file mode 100644 index 0000000000..974022e779 --- /dev/null +++ b/client/src/store/delegation/types.ts @@ -0,0 +1,29 @@ +export interface DelegationData { + calculatingUQScoreMode: 'score' | 'sign'; + delegationPrimaryAddress?: string; + delegationSecondaryAddress?: string; + isDelegationCalculatingUQScoreModalOpen: boolean; + isDelegationCompleted: boolean; + isDelegationConnectModalOpen: boolean; + isDelegationInProgress: boolean; + primaryAddressScore?: number; + secondaryAddressScore?: number; +} + +export interface DelegationMethods { + reset: () => void; + setCalculatingUQScoreMode: (payload: DelegationData['calculatingUQScoreMode']) => void; + setDelegationPrimaryAddress: (payload: DelegationData['delegationPrimaryAddress']) => void; + setDelegationSecondaryAddress: (payload: DelegationData['delegationSecondaryAddress']) => void; + setIsDelegationCalculatingUQScoreModalOpen: ( + payload: DelegationData['isDelegationCalculatingUQScoreModalOpen'], + ) => void; + setIsDelegationCompleted: (payload: DelegationData['isDelegationCompleted']) => void; + setIsDelegationConnectModalOpen: ( + payload: DelegationData['isDelegationConnectModalOpen'], + ) => void; + setIsDelegationInProgress: (payload: DelegationData['isDelegationInProgress']) => void; + setPrimaryAddressScore: (payload: DelegationData['primaryAddressScore']) => void; + setSecondaryAddressScore: (payload: DelegationData['secondaryAddressScore']) => void; + setValuesFromLocalStorage: () => void; +} diff --git a/client/src/views/SettingsView/SettingsView.tsx b/client/src/views/SettingsView/SettingsView.tsx index 5ba61f7b04..4487672c8d 100644 --- a/client/src/views/SettingsView/SettingsView.tsx +++ b/client/src/views/SettingsView/SettingsView.tsx @@ -8,7 +8,6 @@ import SettingsMainInfoBox from 'components/Settings/SettingsMainInfoBox'; import SettingsPatronModeBox from 'components/Settings/SettingsPatronModeBox'; import SettingsShowOnboardingBox from 'components/Settings/SettingsShowOnboardingBox'; import SettingsShowTipsBox from 'components/Settings/SettingsShowTipsBox'; -import SettingsUniquenessScoreBox from 'components/Settings/SettingsUniquenessScoreBox'; import Layout from 'components/shared/Layout'; import useIsProjectAdminMode from 'hooks/helpers/useIsProjectAdminMode'; @@ -24,7 +23,6 @@ const SettingsView = (): ReactElement => { {!isProjectAdminMode && (
- {isConnected && }
)} From ce20c518608a9a81ca2dccf72d5c1baa6dd7a2c0 Mon Sep 17 00:00:00 2001 From: Mateusz Stolecki Date: Fri, 30 Aug 2024 08:18:11 +0200 Subject: [PATCH 050/241] WIP --- backend/tests/api-e2e/test_api_rewards.py | 118 +--------------------- 1 file changed, 5 insertions(+), 113 deletions(-) diff --git a/backend/tests/api-e2e/test_api_rewards.py b/backend/tests/api-e2e/test_api_rewards.py index 6bba31f94e..be6e453180 100644 --- a/backend/tests/api-e2e/test_api_rewards.py +++ b/backend/tests/api-e2e/test_api_rewards.py @@ -1,7 +1,7 @@ import pytest +import time from flask import current_app as app -from app.legacy.core.projects import get_projects_addresses from tests.conftest import Client, UserAccount from tests.helpers.constants import STARTING_EPOCH @@ -14,7 +14,6 @@ def test_rewards_basic( ua_bob: UserAccount, setup_funds, ): - alice_proposals = get_projects_addresses(1)[:3] # lock GLM from two accounts ua_alice.lock(10000) @@ -48,10 +47,14 @@ def test_rewards_basic( # make a pending snapshot res = client.pending_snapshot() assert res["epoch"] > 1 + ua_alice.lock(10000) + time.sleep(120) # make a finalized snapshot res = client.finalized_snapshot() assert res["epoch"] == STARTING_EPOCH + ua_alice.lock(10000) + time.sleep(120) # check if both users will have budget in next epoch res = client.get_user_rewards_in_upcoming_epoch(address=ua_alice.address) @@ -70,114 +73,3 @@ def test_rewards_basic( res = client.get_total_users_rewards_in_epoch(epoch=STARTING_EPOCH) assert res.__contains__(str(alice_upcoming_budget)) assert res.__contains__(str(bob_upcoming_budget)) - - # wait for indexer to catch up - epoch_no = client.wait_for_sync(STARTING_EPOCH + 1) - app.logger.debug(f"indexed epoch: {epoch_no}") - - # make a snapshot - res = client.pending_snapshot() - assert res["epoch"] > 0 - - # ua_alice.allocate(1000, alice_proposals) - # ua_bob.allocate(1000, alice_proposals[:1]) - # - # allocations, _ = client.get_epoch_allocations(STARTING_EPOCH) - # unique_donors = set() - # unique_proposals = set() - # app.logger.debug(f"Allocations: \n {allocations}") - # - # assert len(allocations["allocations"]) == 4 - # for allocation in allocations["allocations"]: - # unique_donors.add(allocation["donor"]) - # unique_proposals.add(allocation["project"]) - # assert int(allocation["amount"]) > 0 - # app.logger.debug(f"Allocations: {allocations}") - # assert len(unique_donors) == 2 - # assert len(unique_proposals) == 3 - - -@pytest.mark.api -def test_allocations_basics( - client: Client, - deployer: UserAccount, - ua_alice: UserAccount, - ua_bob: UserAccount, - setup_funds, -): - alice_proposals = get_projects_addresses(1)[:3] - - # lock GLM from one account - ua_alice.lock(10000) - - # forward time to the beginning of the epoch 2 - client.move_to_next_epoch(STARTING_EPOCH + 1) - - # wait for indexer to catch up - epoch_no = client.wait_for_sync(STARTING_EPOCH + 1) - app.logger.debug(f"indexed epoch: {epoch_no}") - - # make a snapshot - res = client.pending_snapshot() - assert res["epoch"] > (STARTING_EPOCH - 1) - - # Making allocations - nonce, status_code = client.get_allocation_nonce(ua_alice.address) - # Nonce is always 0 - assert status_code == 200, "Nonce status code is different than 200" - - allocation_amount = 1000 - allocation_response_code = ua_alice.allocate(allocation_amount, alice_proposals) - assert ( - allocation_response_code == 201 - ), "Allocation status code is different than 201" - - epoch_allocations, status_code = client.get_epoch_allocations(STARTING_EPOCH) - assert len(epoch_allocations["allocations"]) == len(alice_proposals) - - for allocation in epoch_allocations["allocations"]: - assert allocation["donor"] == ua_alice.address, "Donor address is wrong" - assert int(allocation["amount"]) == allocation_amount - assert allocation["project"], "Proposal address is empty" - app.logger.debug(f"Allocations in epoch 1: {epoch_allocations}") - - assert status_code == 200, "Status code is different than 200" - - # Check user donations - user_allocations, status_code = client.get_user_allocations( - STARTING_EPOCH, ua_alice.address - ) - app.logger.debug(f"User allocations: {user_allocations}") - assert user_allocations["allocations"], "User allocations for given epoch are empty" - assert status_code == 200, "Status code is different than 200" - - # Check donors - donors, status_code = client.get_donors(STARTING_EPOCH) - app.logger.debug(f"Donors: {donors}") - for donor in donors["donors"]: - assert donor == ua_alice.address, "Donor address is wrong" - assert status_code == 200, "Status code is different than 200" - - proposal_address = alice_proposals[0] - # Check donors of particular proposal - proposal_donors, status_code = client.get_proposal_donors( - STARTING_EPOCH, proposal_address - ) - app.logger.debug(f"Proposal donors: {proposal_donors}") - for proposal_donor in proposal_donors: - assert ( - proposal_donor["address"] == ua_alice.address - ), "Proposal donor address is wrong" - assert status_code == 200, "Status code is different than 200" - - # Check leverage - leverage, status_code = client.check_leverage( - proposal_address, ua_alice.address, 1000 - ) - app.logger.debug(f"Leverage: \n{leverage}") - - for matched in leverage["matched"]: - if matched["address"] == proposal_address: - assert matched["value"], "Leverage value is empty" - - assert status_code == 200, "Status code is different than 200" From 615f52127829ab201c3269763e78107ff8f42a06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Miko=C5=82ajczyk?= Date: Fri, 30 Aug 2024 13:36:17 +0200 Subject: [PATCH 051/241] oct-1857: home rewards estimator --- .../src/components/Home/HomeGrid/HomeGrid.tsx | 2 + .../HomeGridRewardsEstimator.module.scss | 20 +++ .../HomeGridRewardsEstimator.tsx | 154 ++++++++++++++++++ ...ardsEstimatorEpochDaysSelector.module.scss | 73 +++++++++ ...eGridRewardsEstimatorEpochDaysSelector.tsx | 57 +++++++ .../index.tsx | 2 + .../types.ts | 4 + ...eGridRewardsEstimatorEstimates.module.scss | 46 ++++++ .../HomeGridRewardsEstimatorEstimates.tsx | 40 +++++ .../index.tsx | 2 + .../types.ts | 7 + ...GridRewardsEstimatorUqSelector.module.scss | 17 ++ .../HomeGridRewardsEstimatorUqSelector.tsx | 30 ++++ .../index.tsx | 2 + .../types.ts | 4 + .../Home/HomeGridRewardsEstimator/index.tsx | 2 + .../Home/HomeGridRewardsEstimator/types.ts | 3 + client/src/locales/en/translation.json | 10 ++ 18 files changed, 475 insertions(+) create mode 100644 client/src/components/Home/HomeGridRewardsEstimator/HomeGridRewardsEstimator.module.scss create mode 100644 client/src/components/Home/HomeGridRewardsEstimator/HomeGridRewardsEstimator.tsx create mode 100644 client/src/components/Home/HomeGridRewardsEstimator/HomeGridRewardsEstimatorEpochDaysSelector/HomeGridRewardsEstimatorEpochDaysSelector.module.scss create mode 100644 client/src/components/Home/HomeGridRewardsEstimator/HomeGridRewardsEstimatorEpochDaysSelector/HomeGridRewardsEstimatorEpochDaysSelector.tsx create mode 100644 client/src/components/Home/HomeGridRewardsEstimator/HomeGridRewardsEstimatorEpochDaysSelector/index.tsx create mode 100644 client/src/components/Home/HomeGridRewardsEstimator/HomeGridRewardsEstimatorEpochDaysSelector/types.ts create mode 100644 client/src/components/Home/HomeGridRewardsEstimator/HomeGridRewardsEstimatorEstimates/HomeGridRewardsEstimatorEstimates.module.scss create mode 100644 client/src/components/Home/HomeGridRewardsEstimator/HomeGridRewardsEstimatorEstimates/HomeGridRewardsEstimatorEstimates.tsx create mode 100644 client/src/components/Home/HomeGridRewardsEstimator/HomeGridRewardsEstimatorEstimates/index.tsx create mode 100644 client/src/components/Home/HomeGridRewardsEstimator/HomeGridRewardsEstimatorEstimates/types.ts create mode 100644 client/src/components/Home/HomeGridRewardsEstimator/HomeGridRewardsEstimatorUqSelector/HomeGridRewardsEstimatorUqSelector.module.scss create mode 100644 client/src/components/Home/HomeGridRewardsEstimator/HomeGridRewardsEstimatorUqSelector/HomeGridRewardsEstimatorUqSelector.tsx create mode 100644 client/src/components/Home/HomeGridRewardsEstimator/HomeGridRewardsEstimatorUqSelector/index.tsx create mode 100644 client/src/components/Home/HomeGridRewardsEstimator/HomeGridRewardsEstimatorUqSelector/types.ts create mode 100644 client/src/components/Home/HomeGridRewardsEstimator/index.tsx create mode 100644 client/src/components/Home/HomeGridRewardsEstimator/types.ts diff --git a/client/src/components/Home/HomeGrid/HomeGrid.tsx b/client/src/components/Home/HomeGrid/HomeGrid.tsx index 6ecb9dfd58..4dd2c7965c 100644 --- a/client/src/components/Home/HomeGrid/HomeGrid.tsx +++ b/client/src/components/Home/HomeGrid/HomeGrid.tsx @@ -7,6 +7,7 @@ import styles from './HomeGrid.module.scss'; import HomeGridCurrentGlmLock from '../HomeGridCurrentGlmLock'; import HomeGridPersonalAllocation from '../HomeGridPersonalAllocation'; +import HomeGridRewardsEstimator from '../HomeGridRewardsEstimator'; import HomeGridUQScore from '../HomeGridUQScore'; const HomeGrid = (): ReactNode => { @@ -17,6 +18,7 @@ const HomeGrid = (): ReactNode => { {isConnected && } + ); }; diff --git a/client/src/components/Home/HomeGridRewardsEstimator/HomeGridRewardsEstimator.module.scss b/client/src/components/Home/HomeGridRewardsEstimator/HomeGridRewardsEstimator.module.scss new file mode 100644 index 0000000000..604f64476d --- /dev/null +++ b/client/src/components/Home/HomeGridRewardsEstimator/HomeGridRewardsEstimator.module.scss @@ -0,0 +1,20 @@ +.root { + padding: 0 2.4rem 2.4rem; + + .divider { + margin-top: 2rem; + width: 100%; + height: 0.1rem; + background-color: $color-octant-grey1; + } + + .withdrawEthButton { + margin-top: 2.4rem; + width: 100%; + } +} + +.glmInput { + margin-bottom: 1.4rem; + margin-top: -1rem; +} diff --git a/client/src/components/Home/HomeGridRewardsEstimator/HomeGridRewardsEstimator.tsx b/client/src/components/Home/HomeGridRewardsEstimator/HomeGridRewardsEstimator.tsx new file mode 100644 index 0000000000..180fce86f4 --- /dev/null +++ b/client/src/components/Home/HomeGridRewardsEstimator/HomeGridRewardsEstimator.tsx @@ -0,0 +1,154 @@ +import { useFormik } from 'formik'; +import debounce from 'lodash/debounce'; +import isEmpty from 'lodash/isEmpty'; +import React, { FC, useCallback, useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; + +import { FormFields } from 'components/Earn/EarnRewardsCalculator/types'; +import { formInitialValues, validationSchema } from 'components/Earn/EarnRewardsCalculator/utils'; +import GridTile from 'components/shared/Grid/GridTile'; +import InputText from 'components/ui/InputText'; +import useGetValuesToDisplay from 'hooks/helpers/useGetValuesToDisplay'; +import useCalculateRewards from 'hooks/mutations/useCalculateRewards'; +import { formatUnitsBigInt } from 'utils/formatUnitsBigInt'; +import { parseUnitsBigInt } from 'utils/parseUnitsBigInt'; +import { comma, floatNumberWithUpTo18DecimalPlaces } from 'utils/regExp'; + +import styles from './HomeGridRewardsEstimator.module.scss'; +import HomeGridRewardsEstimatorEpochDaysSelector from './HomeGridRewardsEstimatorEpochDaysSelector'; +import HomeGridRewardsEstimatorEstimates from './HomeGridRewardsEstimatorEstimates'; +import HomeGridRewardsEstimatorUqSelector from './HomeGridRewardsEstimatorUqSelector'; +import HomeGridRewardsEstimatorProps from './types'; + +const HomeGridRewardsEstimator: FC = ({ className }) => { + const { t } = useTranslation('translation', { + keyPrefix: 'components.home.homeGridRewardsEstimator', + }); + + const getValuesToDisplay = useGetValuesToDisplay(); + + const { + data: calculateRewards, + mutateAsync: mutateAsyncRewardsCalculator, + reset: resetCalculateRewards, + isPending: isPendingCalculateRewards, + } = useCalculateRewards(); + + // eslint-disable-next-line react-hooks/exhaustive-deps + const fetchEstimatedRewardsDebounced = useCallback( + debounce(({ amountGlm, numberOfEpochs }) => { + const amountGlmWEI = formatUnitsBigInt(parseUnitsBigInt(amountGlm, 'ether'), 'wei'); + resetCalculateRewards(); + mutateAsyncRewardsCalculator({ amountGlm: amountGlmWEI, numberOfEpochs }); + }, 300), + [], + ); + + const formik = useFormik({ + initialValues: formInitialValues, + onSubmit: values => + fetchEstimatedRewardsDebounced({ + amountGlm: values.valueCrypto, + numberOfEpochs: values.numberOfEpochs, + }), + validateOnChange: true, + validationSchema: validationSchema(t), + }); + + const onCryptoValueChange = (value: string) => { + const valueComma = value.replace(comma, '.'); + + if (valueComma && !floatNumberWithUpTo18DecimalPlaces.test(valueComma)) { + return; + } + + formik.setFieldValue('valueCrypto', valueComma || ''); + }; + + const estimatedRewards = + calculateRewards && isEmpty(formik.errors) && !isEmpty(formik.values.valueCrypto) + ? getValuesToDisplay({ + cryptoCurrency: 'ethereum', + showCryptoSuffix: true, + valueCrypto: parseUnitsBigInt(calculateRewards.budget, 'wei'), + }) + : undefined; + + const matchFunding = + calculateRewards && isEmpty(formik.errors) && !isEmpty(formik.values.valueCrypto) + ? getValuesToDisplay({ + cryptoCurrency: 'ethereum', + showCryptoSuffix: true, + valueCrypto: + (parseUnitsBigInt(calculateRewards.matchedFunding, 'wei') * + (formik.values.isUqScoreOver20 ? 100n : 20n)) / + 100n, + }) + : undefined; + + useEffect(() => { + if (!formik.values.valueCrypto || !formik.values.numberOfEpochs) { + return; + } + formik.validateForm().then(errors => { + if (!isEmpty(errors)) { + return; + } + fetchEstimatedRewardsDebounced({ + amountGlm: formik.values.valueCrypto, + numberOfEpochs: formik.values.numberOfEpochs, + }); + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [formik.values.valueCrypto, formik.values.numberOfEpochs]); + + useEffect(() => { + return () => { + resetCalculateRewards(); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return ( + { + formik.setFieldValue('isUqScoreOver20', isUqScoreOver20); + }} + /> + } + > +
+ onCryptoValueChange(e.target.value)} + suffix="GLM" + value={formik.values.valueCrypto} + /> + { + formik.setFieldValue('numberOfEpochs', epoch); + }} + /> + +
+
+ ); +}; + +export default HomeGridRewardsEstimator; diff --git a/client/src/components/Home/HomeGridRewardsEstimator/HomeGridRewardsEstimatorEpochDaysSelector/HomeGridRewardsEstimatorEpochDaysSelector.module.scss b/client/src/components/Home/HomeGridRewardsEstimator/HomeGridRewardsEstimatorEpochDaysSelector/HomeGridRewardsEstimatorEpochDaysSelector.module.scss new file mode 100644 index 0000000000..f0c58e6ab3 --- /dev/null +++ b/client/src/components/Home/HomeGridRewardsEstimator/HomeGridRewardsEstimatorEpochDaysSelector/HomeGridRewardsEstimatorEpochDaysSelector.module.scss @@ -0,0 +1,73 @@ +.root { + width: 100%; + + .daysSelectorLabel, + .estimatesLabel { + color: $color-octant-grey5; + font-size: $font-size-10; + font-weight: $font-weight-bold; + margin-bottom: 0.6rem; + text-align: left; + width: 100%; + } + + .daysSelector { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + height: 4.8rem; + background: $color-white; + border-radius: $border-radius-04; + border: 0.1rem solid $color-octant-grey1; + padding: 0.4rem 1.6rem 0.4rem 0.6rem; + + .daysWrapper { + display: flex; + flex: 1; + } + + .day { + position: relative; + cursor: pointer; + color: $color-octant-grey2; + font-weight: $font-weight-bold; + display: flex; + align-items: center; + justify-content: center; + flex: 1; + height: 3.8rem; + + .dayLabel { + z-index: $z-index-2; + } + + &.isSelected { + color: $color-octant-dark; + transition: all $transition-time-4; + } + + .selectedItemBackground { + position: absolute; + z-index: $z-index-1; + width: 100%; + height: 100%; + top: 0; + left: 0; + background-color: $color-octant-grey1; + border-radius: $border-radius-04; + } + } + + .suffix { + font-weight: $font-weight-bold; + padding: 0.4rem 0.8rem; + font-size: 1.2rem; + color: $color-octant-dark; + background: $color-octant-grey1; + border-radius: 0.4rem; + margin-left: 0.8rem; + text-transform: uppercase; + } + } +} diff --git a/client/src/components/Home/HomeGridRewardsEstimator/HomeGridRewardsEstimatorEpochDaysSelector/HomeGridRewardsEstimatorEpochDaysSelector.tsx b/client/src/components/Home/HomeGridRewardsEstimator/HomeGridRewardsEstimatorEpochDaysSelector/HomeGridRewardsEstimatorEpochDaysSelector.tsx new file mode 100644 index 0000000000..c5c6286144 --- /dev/null +++ b/client/src/components/Home/HomeGridRewardsEstimator/HomeGridRewardsEstimatorEpochDaysSelector/HomeGridRewardsEstimatorEpochDaysSelector.tsx @@ -0,0 +1,57 @@ +import cx from 'classnames'; +import { motion } from 'framer-motion'; +import React, { FC } from 'react'; +import { useTranslation } from 'react-i18next'; + +import styles from './HomeGridRewardsEstimatorEpochDaysSelector.module.scss'; +import HomeGridRewardsEstimatorEpochDaysSelectorProps from './types'; + +const HomeGridRewardsEstimatorEpochDaysSelector: FC< + HomeGridRewardsEstimatorEpochDaysSelectorProps +> = ({ numberOfEpochs, onChange }) => { + const { i18n, t } = useTranslation('translation', { + keyPrefix: 'components.home.homeGridRewardsEstimator', + }); + + const epochDurationInDays = 90; + const selectorOptions = [1, 2, 3]; // Epochs + + const dataTest = 'HomeGridRewardsEstimatorEpochDaysSelector'; + + return ( +
+
+ {t('lockForEpoch', { count: numberOfEpochs })} +
+
+
+ {selectorOptions.map(epoch => ( +
onChange(epoch)} + > + + {epoch * epochDurationInDays} + + {numberOfEpochs === epoch ? ( + + ) : null} +
+ ))} +
+
+ {i18n.t('common.days')} +
+
+
+ ); +}; + +export default HomeGridRewardsEstimatorEpochDaysSelector; diff --git a/client/src/components/Home/HomeGridRewardsEstimator/HomeGridRewardsEstimatorEpochDaysSelector/index.tsx b/client/src/components/Home/HomeGridRewardsEstimator/HomeGridRewardsEstimatorEpochDaysSelector/index.tsx new file mode 100644 index 0000000000..7916b68ec8 --- /dev/null +++ b/client/src/components/Home/HomeGridRewardsEstimator/HomeGridRewardsEstimatorEpochDaysSelector/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './HomeGridRewardsEstimatorEpochDaysSelector'; diff --git a/client/src/components/Home/HomeGridRewardsEstimator/HomeGridRewardsEstimatorEpochDaysSelector/types.ts b/client/src/components/Home/HomeGridRewardsEstimator/HomeGridRewardsEstimatorEpochDaysSelector/types.ts new file mode 100644 index 0000000000..8225e08073 --- /dev/null +++ b/client/src/components/Home/HomeGridRewardsEstimator/HomeGridRewardsEstimatorEpochDaysSelector/types.ts @@ -0,0 +1,4 @@ +export default interface HomeGridRewardsEstimatorEpochDaysSelectorProps { + numberOfEpochs: number; + onChange: (numberOfEpochs: number) => void; +} diff --git a/client/src/components/Home/HomeGridRewardsEstimator/HomeGridRewardsEstimatorEstimates/HomeGridRewardsEstimatorEstimates.module.scss b/client/src/components/Home/HomeGridRewardsEstimator/HomeGridRewardsEstimatorEstimates/HomeGridRewardsEstimatorEstimates.module.scss new file mode 100644 index 0000000000..110cfddc94 --- /dev/null +++ b/client/src/components/Home/HomeGridRewardsEstimator/HomeGridRewardsEstimatorEstimates/HomeGridRewardsEstimatorEstimates.module.scss @@ -0,0 +1,46 @@ +.root { + width: 100%; + display: flex; + align-items: center; + margin-top: 1.4rem; + + .column { + flex: 1; + + &:first-child { + margin-right: 0.8rem; + } + + .label { + text-align: left; + font-size: $font-size-10; + font-weight: $font-weight-bold; + color: $color-octant-grey5; + margin-bottom: 0.6rem; + } + + .valueBox { + display: flex; + align-items: center; + background-color: $color-octant-grey8; + border-radius: $border-radius-04; + height: 4.8rem; + border: 0.1rem solid $color-octant-grey1; + padding-left: 1.6rem; + + .value { + color: $color-octant-dark; + font-size: $font-size-14; + font-weight: $font-weight-bold; + } + + .value { + &.showSkeleton { + @include skeleton($color-octant-grey1, $color-octant-grey12); + width: 10.4rem; + height: 1.7rem; + } + } + } + } +} diff --git a/client/src/components/Home/HomeGridRewardsEstimator/HomeGridRewardsEstimatorEstimates/HomeGridRewardsEstimatorEstimates.tsx b/client/src/components/Home/HomeGridRewardsEstimator/HomeGridRewardsEstimatorEstimates/HomeGridRewardsEstimatorEstimates.tsx new file mode 100644 index 0000000000..4932d91ee0 --- /dev/null +++ b/client/src/components/Home/HomeGridRewardsEstimator/HomeGridRewardsEstimatorEstimates/HomeGridRewardsEstimatorEstimates.tsx @@ -0,0 +1,40 @@ +import cx from 'classnames'; +import React, { FC } from 'react'; +import { useTranslation } from 'react-i18next'; + +import styles from './HomeGridRewardsEstimatorEstimates.module.scss'; +import HomeGridRewardsEstimatorEstimatesProps from './types'; + +const HomeGridRewardsEstimatorEstimates: FC = ({ + estimatedRewards, + matchFunding, + isLoading, +}) => { + const { i18n } = useTranslation('translation'); + + const dataTest = 'HomeGridRewardsEstimatorEstimates'; + + return ( +
+
+
{i18n.t('common.rewards', { rewards: '' })}
+ +
+
+ {estimatedRewards ? estimatedRewards.primary : ''} +
+
+
+
+
{i18n.t('common.matchFunding')}
+
+
+ {matchFunding ? matchFunding.primary : ''} +
+
+
+
+ ); +}; + +export default HomeGridRewardsEstimatorEstimates; diff --git a/client/src/components/Home/HomeGridRewardsEstimator/HomeGridRewardsEstimatorEstimates/index.tsx b/client/src/components/Home/HomeGridRewardsEstimator/HomeGridRewardsEstimatorEstimates/index.tsx new file mode 100644 index 0000000000..3b7ee2c94b --- /dev/null +++ b/client/src/components/Home/HomeGridRewardsEstimator/HomeGridRewardsEstimatorEstimates/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './HomeGridRewardsEstimatorEstimates'; diff --git a/client/src/components/Home/HomeGridRewardsEstimator/HomeGridRewardsEstimatorEstimates/types.ts b/client/src/components/Home/HomeGridRewardsEstimator/HomeGridRewardsEstimatorEstimates/types.ts new file mode 100644 index 0000000000..db56522e8c --- /dev/null +++ b/client/src/components/Home/HomeGridRewardsEstimator/HomeGridRewardsEstimatorEstimates/types.ts @@ -0,0 +1,7 @@ +import { GetValuesToDisplayReturnType } from 'hooks/helpers/useGetValuesToDisplay'; + +export default interface HomeGridRewardsEstimatorEstimatesProps { + estimatedRewards?: GetValuesToDisplayReturnType; + isLoading?: boolean; + matchFunding?: GetValuesToDisplayReturnType; +} diff --git a/client/src/components/Home/HomeGridRewardsEstimator/HomeGridRewardsEstimatorUqSelector/HomeGridRewardsEstimatorUqSelector.module.scss b/client/src/components/Home/HomeGridRewardsEstimator/HomeGridRewardsEstimatorUqSelector/HomeGridRewardsEstimatorUqSelector.module.scss new file mode 100644 index 0000000000..97e427a3b9 --- /dev/null +++ b/client/src/components/Home/HomeGridRewardsEstimator/HomeGridRewardsEstimatorUqSelector/HomeGridRewardsEstimatorUqSelector.module.scss @@ -0,0 +1,17 @@ +.root { + margin-left: auto; + display: flex; + border-radius: $border-radius-10; + background-color: $color-octant-grey8; + width: 11.2rem; + height: 3.2rem; + padding: 0 0.8rem 0 1.6rem; + align-items: center; + + .label { + font-size: $font-size-10; + font-weight: $font-weight-bold; + color: $color-octant-grey5; + margin-right: auto; + } +} diff --git a/client/src/components/Home/HomeGridRewardsEstimator/HomeGridRewardsEstimatorUqSelector/HomeGridRewardsEstimatorUqSelector.tsx b/client/src/components/Home/HomeGridRewardsEstimator/HomeGridRewardsEstimatorUqSelector/HomeGridRewardsEstimatorUqSelector.tsx new file mode 100644 index 0000000000..7369ebd8d7 --- /dev/null +++ b/client/src/components/Home/HomeGridRewardsEstimator/HomeGridRewardsEstimatorUqSelector/HomeGridRewardsEstimatorUqSelector.tsx @@ -0,0 +1,30 @@ +import React, { FC } from 'react'; +import { useTranslation } from 'react-i18next'; + +import InputToggle from 'components/ui/InputToggle'; + +import styles from './HomeGridRewardsEstimatorUqSelector.module.scss'; +import HomeGridRewardsEstimatorUqSelectorProps from './types'; + +const HomeGridRewardsEstimatorUqSelector: FC = ({ + isUqScoreOver20, + onChange, +}) => { + const { t } = useTranslation('translation', { + keyPrefix: 'components.home.homeGridRewardsEstimator', + }); + + const dataTest = 'HomeGridRewardsEstimatorUqSelector'; + + return ( +
+
{t('uq20+')}
+ onChange(isChecked)} + /> +
+ ); +}; + +export default HomeGridRewardsEstimatorUqSelector; diff --git a/client/src/components/Home/HomeGridRewardsEstimator/HomeGridRewardsEstimatorUqSelector/index.tsx b/client/src/components/Home/HomeGridRewardsEstimator/HomeGridRewardsEstimatorUqSelector/index.tsx new file mode 100644 index 0000000000..f6b32aacb8 --- /dev/null +++ b/client/src/components/Home/HomeGridRewardsEstimator/HomeGridRewardsEstimatorUqSelector/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './HomeGridRewardsEstimatorUqSelector'; diff --git a/client/src/components/Home/HomeGridRewardsEstimator/HomeGridRewardsEstimatorUqSelector/types.ts b/client/src/components/Home/HomeGridRewardsEstimator/HomeGridRewardsEstimatorUqSelector/types.ts new file mode 100644 index 0000000000..387849cf71 --- /dev/null +++ b/client/src/components/Home/HomeGridRewardsEstimator/HomeGridRewardsEstimatorUqSelector/types.ts @@ -0,0 +1,4 @@ +export default interface HomeGridRewardsEstimatorUqSelectorProps { + isUqScoreOver20: boolean; + onChange: (isUqScoreOver20: boolean) => void; +} diff --git a/client/src/components/Home/HomeGridRewardsEstimator/index.tsx b/client/src/components/Home/HomeGridRewardsEstimator/index.tsx new file mode 100644 index 0000000000..53745904a9 --- /dev/null +++ b/client/src/components/Home/HomeGridRewardsEstimator/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './HomeGridRewardsEstimator'; diff --git a/client/src/components/Home/HomeGridRewardsEstimator/types.ts b/client/src/components/Home/HomeGridRewardsEstimator/types.ts new file mode 100644 index 0000000000..dddbc46c3f --- /dev/null +++ b/client/src/components/Home/HomeGridRewardsEstimator/types.ts @@ -0,0 +1,3 @@ +export default interface HomeGridRewardsEstimatorProps { + className?: string; +} diff --git a/client/src/locales/en/translation.json b/client/src/locales/en/translation.json index 6b924ed620..c1b4cc7726 100644 --- a/client/src/locales/en/translation.json +++ b/client/src/locales/en/translation.json @@ -132,6 +132,16 @@ "modalRecalculatingScore": { "recalculatingScore":"Recalculating score" } + }, + "homeGridRewardsEstimator": { + "rewardsEstimator": "Rewards estimator", + "enterGLMAmount": "Enter a GLM amount", + "lockForEpoch_one": "Lock for {{count}} epoch", + "lockForEpoch_other": "Lock for {{count}} epochs", + "uq20+": "UQ 20+", + "errors": { + "valueCryptoTooBig": "That isn’t a valid amount" + } } }, "settings": { From 9e071b7f6a5ca8915d9a4e6a8da03000c3500007 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Miko=C5=82ajczyk?= Date: Mon, 2 Sep 2024 08:00:09 +0200 Subject: [PATCH 052/241] oct-1868: cr fixes --- .../components/Home/HomeRewards/HomeRewards.tsx | 4 ++-- client/src/locales/en/translation.json | 15 +++++++-------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/client/src/components/Home/HomeRewards/HomeRewards.tsx b/client/src/components/Home/HomeRewards/HomeRewards.tsx index bd04bca289..d3236541f9 100644 --- a/client/src/components/Home/HomeRewards/HomeRewards.tsx +++ b/client/src/components/Home/HomeRewards/HomeRewards.tsx @@ -24,7 +24,7 @@ const HomeRewards = (): ReactNode => { const getValuesToDisplay = useGetValuesToDisplay(); // We count only rewards from epochs user did an action -- allocation or was a patron. - const totalRewardsUsed = individualRewardAllEpochs.reduce((acc, curr, currentIndex) => { + const totalRewards = individualRewardAllEpochs.reduce((acc, curr, currentIndex) => { const hasUserAlreadyDoneAllocationInGivenEpoch = userAllocationsAllEpochs[currentIndex]?.hasUserAlreadyDoneAllocation || false; const wasPatronInGivenEpoch = @@ -43,7 +43,7 @@ const HomeRewards = (): ReactNode => { const totalRewardsToDisplay = getValuesToDisplay({ cryptoCurrency: 'ethereum', showCryptoSuffix: true, - valueCrypto: totalRewardsUsed, + valueCrypto: totalRewards, }).primary; const tiles = [ diff --git a/client/src/locales/en/translation.json b/client/src/locales/en/translation.json index 0d4d43ab06..1fcfcf3e41 100644 --- a/client/src/locales/en/translation.json +++ b/client/src/locales/en/translation.json @@ -61,11 +61,11 @@ } }, "home": { -"homeRewards": { - "currentRewards": "Current rewards", - "totalRewards":"Total rewards", - "rewardsRate": "Rewards rate" -} + "homeRewards": { + "currentRewards": "Current rewards", + "totalRewards": "Total rewards", + "rewardsRate": "Rewards rate" + } }, "settings": { "patronMode": { @@ -331,7 +331,6 @@ "docs": "Docs", "brandAssets": "Brand assets", "termsOfUse": "Terms of use" - } }, "main": { @@ -580,7 +579,7 @@ "octantInfo": "Octant is a platform for experiments in decentralized governance that reward participation. Learn more below.", "recalculatingScore": "Recalculating score", "calculatingScore": "Calculating score", - "checkingPassportScore":"Checking Passport score", + "checkingPassportScore": "Checking Passport score", "finished": "Finished", "checkingAllowlist": "Checking allowlist", "checkOutDocs": "Check out the docs", @@ -616,4 +615,4 @@ "information": "We're synchronizing things to prepare the
next epoch, so the app will be unavailable
for a little while. Please check back soon." } } -} +} \ No newline at end of file From bec177d7afaaec3d7a5542d8d1c13cca662a84f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Miko=C5=82ajczyk?= Date: Mon, 2 Sep 2024 08:16:51 +0200 Subject: [PATCH 053/241] oct-1871: cr fixes --- .../src/components/Home/HomeGrid/HomeGrid.tsx | 3 +-- .../ModalLockGlm/LockGlm/LockGlm.tsx | 9 +++---- .../LockGlmBudget/LockGlmBudget.tsx | 3 +-- .../LockGlmNotification.module.scss | 11 -------- .../LockGlmNotification.tsx | 21 +++------------ .../LockGlmNotificationLinkButton.module.scss | 10 +++++++ .../LockGlmNotificationLinkButton.tsx | 26 +++++++++++++++++++ .../LockGlmNotificationLinkButton/index.tsx | 2 ++ .../LockGlmNotificationLinkButton/types.ts | 6 +++++ .../ModalLockGlm/LockGlmTabs/LockGlmTabs.tsx | 3 +-- 10 files changed, 54 insertions(+), 40 deletions(-) create mode 100644 client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmNotificationLinkButton/LockGlmNotificationLinkButton.module.scss create mode 100644 client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmNotificationLinkButton/LockGlmNotificationLinkButton.tsx create mode 100644 client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmNotificationLinkButton/index.tsx create mode 100644 client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmNotificationLinkButton/types.ts diff --git a/client/src/components/Home/HomeGrid/HomeGrid.tsx b/client/src/components/Home/HomeGrid/HomeGrid.tsx index 7457c38e3b..c4c3354133 100644 --- a/client/src/components/Home/HomeGrid/HomeGrid.tsx +++ b/client/src/components/Home/HomeGrid/HomeGrid.tsx @@ -1,11 +1,10 @@ import React, { memo, ReactNode } from 'react'; +import HomeGridCurrentGlmLock from 'components/Home/HomeGridCurrentGlmLock'; import Grid from 'components/shared/Grid'; import styles from './HomeGrid.module.scss'; -import HomeGridCurrentGlmLock from '../HomeGridCurrentGlmLock'; - const HomeGrid = (): ReactNode => ( diff --git a/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlm/LockGlm.tsx b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlm/LockGlm.tsx index 110cb4d054..283cbce4ad 100644 --- a/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlm/LockGlm.tsx +++ b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlm/LockGlm.tsx @@ -4,6 +4,10 @@ import { useTranslation } from 'react-i18next'; import { useAccount, useWalletClient, usePublicClient, useWaitForTransactionReceipt } from 'wagmi'; import { apiGetSafeTransactions } from 'api/calls/safeTransactions'; +import LockGlmBudget from 'components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmBudget'; +import LockGlmNotification from 'components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmNotification'; +import LockGlmStepper from 'components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmStepper'; +import LockGlmTabs from 'components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmTabs'; import networkConfig from 'constants/networkConfig'; import env from 'env'; import { writeContractERC20 } from 'hooks/contracts/writeContracts'; @@ -26,11 +30,6 @@ import styles from './LockGlm.module.scss'; import LockGlmProps, { Step, OnReset } from './types'; import { formInitialValues, validationSchema } from './utils'; -import LockGlmBudget from '../LockGlmBudget'; -import LockGlmNotification from '../LockGlmNotification'; -import LockGlmStepper from '../LockGlmStepper'; -import LockGlmTabs from '../LockGlmTabs'; - const LockGlm: FC = ({ currentMode, onCurrentModeChange, onCloseModal }) => { const { i18n } = useTranslation(); const { address } = useAccount(); diff --git a/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmBudget/LockGlmBudget.tsx b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmBudget/LockGlmBudget.tsx index 030e04b4f4..b5d65bbb09 100644 --- a/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmBudget/LockGlmBudget.tsx +++ b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmBudget/LockGlmBudget.tsx @@ -2,12 +2,11 @@ import { useFormikContext } from 'formik'; import React, { FC } from 'react'; import { FormFields } from 'components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlm/types'; +import LockGlmBudgetBox from 'components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmBudgetBox'; import styles from './LockGlmBudget.module.scss'; import LockGlmBudgetProps from './types'; -import LockGlmBudgetBox from '../LockGlmBudgetBox'; - const LockGlmBudget: FC = ({ isVisible }) => { const { errors } = useFormikContext(); diff --git a/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmNotification/LockGlmNotification.module.scss b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmNotification/LockGlmNotification.module.scss index 8c77ef5984..5199cae37c 100644 --- a/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmNotification/LockGlmNotification.module.scss +++ b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmNotification/LockGlmNotification.module.scss @@ -20,16 +20,5 @@ .text { color: $color-octant-grey5; text-align: left; - - .link { - font-size: $font-size-12; - min-height: 2rem; - font-weight: $font-weight-semibold; - - &:hover { - cursor: pointer; - transform: none; - } - } } } diff --git a/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmNotification/LockGlmNotification.tsx b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmNotification/LockGlmNotification.tsx index 27c3a22084..1d2a362a29 100644 --- a/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmNotification/LockGlmNotification.tsx +++ b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmNotification/LockGlmNotification.tsx @@ -2,28 +2,13 @@ import React, { FC, useMemo } from 'react'; import { Trans, useTranslation } from 'react-i18next'; import BoxRounded from 'components/ui/BoxRounded'; -import Button from 'components/ui/Button'; import Svg from 'components/ui/Svg'; -import networkConfig from 'constants/networkConfig'; -import { arrowTopRight, checkMark, notificationIconWarning } from 'svg/misc'; +import { checkMark, notificationIconWarning } from 'svg/misc'; import styles from './LockGlmNotification.module.scss'; import LockGlmNotificationProps from './types'; -const ButtonLinkWithIcon: FC<{ children?: React.ReactNode; transactionHash: string }> = ({ - children, - transactionHash, -}) => { - return ( - - ); -}; +import LockGlmNotificationLinkButton from '../LockGlmNotificationLinkButton'; const LockGlmNotification: FC = ({ className, @@ -80,7 +65,7 @@ const LockGlmNotification: FC = ({ ] + ? [] : undefined } i18nKey={text} diff --git a/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmNotificationLinkButton/LockGlmNotificationLinkButton.module.scss b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmNotificationLinkButton/LockGlmNotificationLinkButton.module.scss new file mode 100644 index 0000000000..c430af7485 --- /dev/null +++ b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmNotificationLinkButton/LockGlmNotificationLinkButton.module.scss @@ -0,0 +1,10 @@ +.root { + font-size: $font-size-12; + min-height: 2rem; + font-weight: $font-weight-semibold; + + &:hover { + cursor: pointer; + transform: none; + } +} diff --git a/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmNotificationLinkButton/LockGlmNotificationLinkButton.tsx b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmNotificationLinkButton/LockGlmNotificationLinkButton.tsx new file mode 100644 index 0000000000..752f2b2ab3 --- /dev/null +++ b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmNotificationLinkButton/LockGlmNotificationLinkButton.tsx @@ -0,0 +1,26 @@ +import React, { FC, memo } from 'react'; + +import Button from 'components/ui/Button'; +import Svg from 'components/ui/Svg'; +import networkConfig from 'constants/networkConfig'; +import { arrowTopRight } from 'svg/misc'; + +import styles from './LockGlmNotificationLinkButton.module.scss'; +import LockGlmNotificationLinkButtonProps from './types'; + +const LockGlmNotificationLinkButton: FC = ({ + children, + transactionHash, +}) => { + return ( + + ); +}; + +export default memo(LockGlmNotificationLinkButton); diff --git a/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmNotificationLinkButton/index.tsx b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmNotificationLinkButton/index.tsx new file mode 100644 index 0000000000..8941cd49be --- /dev/null +++ b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmNotificationLinkButton/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './LockGlmNotificationLinkButton'; diff --git a/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmNotificationLinkButton/types.ts b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmNotificationLinkButton/types.ts new file mode 100644 index 0000000000..c96ea3baec --- /dev/null +++ b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmNotificationLinkButton/types.ts @@ -0,0 +1,6 @@ +import { ReactNode } from 'react'; + +export default interface LockGlmNotificationLinkButtonProps { + children?: ReactNode; + transactionHash: string; +} diff --git a/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmTabs/LockGlmTabs.tsx b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmTabs/LockGlmTabs.tsx index f99fb10e8e..dc6615d128 100644 --- a/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmTabs/LockGlmTabs.tsx +++ b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmTabs/LockGlmTabs.tsx @@ -4,6 +4,7 @@ import React, { FC, useMemo, useRef } from 'react'; import { useTranslation } from 'react-i18next'; import { FormFields } from 'components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlm/types'; +import LockGlmTabsInputs from 'components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmTabsInputs'; import BoxRounded from 'components/ui/BoxRounded'; import Button from 'components/ui/Button'; import ButtonProps from 'components/ui/Button/types'; @@ -16,8 +17,6 @@ import { parseUnitsBigInt } from 'utils/parseUnitsBigInt'; import styles from './LockGlmTabs.module.scss'; import LockGlmTabsProps from './types'; -import LockGlmTabsInputs from '../LockGlmTabsInputs'; - const LockGlmTabs: FC = ({ buttonUseMaxRef, className, From 44a889f3186fadc5cf48305e1faeef05e7611404 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Garbaci=C5=84ski?= <57113816+kgarbacinski@users.noreply.github.com> Date: Mon, 2 Sep 2024 14:36:01 +0200 Subject: [PATCH 054/241] OCT-1837 & OCT-1838: Upgrade metrics pie chart for unused MR (#383) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description ## Definition of Done 1. [ ] Acceptance criteria are met. 2. [ ] PR is manually tested before the merge by developer(s). - [ ] Happy path is manually checked. 3. [ ] PR is manually tested by QA when their assistance is required (1). - [ ] Octant Areas & Test Cases are checked for impact and updated if required (2). 4. [ ] Unit tests are added unless there is a reason to omit them. 5. [ ] Automated tests are added when required. 6. [ ] The code is merged. 7. [ ] Tech documentation is added / updated, reviewed and approved (including mandatory approval by a code owner, should such exist for changed files). - [ ] BE: Swagger documentation is updated. 8. [ ] When required by QA: - [ ] Deployed to the relevant environment. - [ ] Passed system tests. --- (1) Developer(s) in coordination with QA decide whether it's required. For small tickets introducing small changes QA assistance is most probably not required. (2) [Octant Areas & Test Cases](https://docs.google.com/spreadsheets/d/1cRe6dxuKJV3a4ZskAwWEPvrFkQm6rEfyUCYwLTYw_Cc). --------- Co-authored-by: Andrzej Ziółek --- .../octant_rewards/leftover/__init__.py | 3 ++ .../leftover/with_ppf_and_unused.py | 11 +++++ .../app/engine/projects/rewards/__init__.py | 17 +++++++- .../projects/rewards/allocations/__init__.py | 12 ++++-- .../engine/projects/rewards/preliminary.py | 13 +++++- .../projects/rewards/threshold/__init__.py | 10 +++++ .../projects/rewards/threshold/preliminary.py | 24 +++++++++++ backend/app/infrastructure/routes/epochs.py | 4 ++ backend/app/modules/dto.py | 2 + .../general/service/finalized.py | 34 +++++++++++++++ .../octant_rewards/general/service/pending.py | 35 +++++++++++----- .../projects/rewards/service/finalizing.py | 6 ++- backend/tests/conftest.py | 33 ++++++--------- backend/tests/helpers/constants.py | 8 ++++ backend/tests/helpers/finalized_snapshots.py | 18 ++++++++ .../tests/modules/octant_rewards/conftest.py | 42 +++++++++++++++++++ .../modules/octant_rewards/helpers/checker.py | 2 + .../test_calculated_octant_rewards.py | 2 + .../test_finalized_octant_rewards.py | 12 ++++++ .../test_pending_octant_rewards.py | 29 ++++++++++++- .../finalized/test_finalizing_snapshots.py | 6 ++- client/src/api/calls/epochInfo.ts | 1 + .../Metrics/MetricsEpoch/MetricsEpoch.tsx | 1 - .../MetricsEpochGridFundsUsage.tsx | 8 +--- .../MetricsEpochGridFundsUsage/types.ts | 1 - client/src/hooks/queries/useEpochInfo.ts | 4 ++ 26 files changed, 288 insertions(+), 50 deletions(-) create mode 100644 backend/tests/helpers/finalized_snapshots.py create mode 100644 backend/tests/modules/octant_rewards/conftest.py diff --git a/backend/app/engine/octant_rewards/leftover/__init__.py b/backend/app/engine/octant_rewards/leftover/__init__.py index 144069a783..e9295e963c 100644 --- a/backend/app/engine/octant_rewards/leftover/__init__.py +++ b/backend/app/engine/octant_rewards/leftover/__init__.py @@ -18,3 +18,6 @@ class Leftover(ABC): @abstractmethod def calculate_leftover(self, payload: LeftoverPayload) -> int: pass + + def extract_unused_matched_rewards(self, *args, **kwargs) -> int: + return 0 diff --git a/backend/app/engine/octant_rewards/leftover/with_ppf_and_unused.py b/backend/app/engine/octant_rewards/leftover/with_ppf_and_unused.py index 3faf8085b2..3f0d57acc3 100644 --- a/backend/app/engine/octant_rewards/leftover/with_ppf_and_unused.py +++ b/backend/app/engine/octant_rewards/leftover/with_ppf_and_unused.py @@ -15,3 +15,14 @@ def calculate_leftover(self, payload: LeftoverPayload) -> int: - payload.total_withdrawals + unused_matched_rewards ) + + def extract_unused_matched_rewards(self, leftover, payload) -> int: + extra_individual_rewards = int(payload.ppf / 2) + return ( + leftover + - payload.staking_proceeds + + payload.operational_cost + + payload.community_fund + + extra_individual_rewards + + payload.total_withdrawals + ) diff --git a/backend/app/engine/projects/rewards/__init__.py b/backend/app/engine/projects/rewards/__init__.py index 95d704bd68..5c88af2a5f 100644 --- a/backend/app/engine/projects/rewards/__init__.py +++ b/backend/app/engine/projects/rewards/__init__.py @@ -1,6 +1,7 @@ from abc import ABC, abstractmethod +from collections import namedtuple from dataclasses import dataclass, field -from typing import List, Optional +from typing import List, Optional, Dict from dataclass_wizard import JSONWizard @@ -41,6 +42,11 @@ class ProjectRewardsResult: threshold: Optional[int] = None +AllocationsBelowThreshold = namedtuple( + "AllocationsBelowThreshold", ["below_threshold", "total"] +) + + @dataclass class ProjectRewards(ABC): projects_allocations: ProjectAllocations = field(init=False) @@ -54,3 +60,12 @@ def calculate_project_rewards( def calculate_threshold(self, total_allocated: int, projects: List[str]) -> None: return None + + def get_total_allocations_below_threshold( + self, allocations: Dict[str, List[AllocationItem]] + ) -> AllocationsBelowThreshold: + allocations_sum = sum( + sum(int(item.amount) for item in project_allocations) + for project_allocations in allocations.values() + ) + return AllocationsBelowThreshold(0, allocations_sum) diff --git a/backend/app/engine/projects/rewards/allocations/__init__.py b/backend/app/engine/projects/rewards/allocations/__init__.py index 5a6856393a..3123dca6ae 100644 --- a/backend/app/engine/projects/rewards/allocations/__init__.py +++ b/backend/app/engine/projects/rewards/allocations/__init__.py @@ -33,10 +33,9 @@ def _calc_allocations( ) -> Union[int, Decimal]: ... - def group_allocations_by_projects( + def segregate_allocations( self, payload: ProjectAllocationsPayload - ) -> (Dict[str, List], List[ProjectSumAllocationsDTO], Union[int, Decimal]): - result_allocations = [] + ) -> Dict[str, List]: grouped_allocations = { key: list(group) for key, group in groupby( @@ -44,7 +43,14 @@ def group_allocations_by_projects( key=lambda a: a.project_address, ) } + return grouped_allocations + + def group_allocations_by_projects( + self, payload: ProjectAllocationsPayload + ) -> (Dict[str, List], List[ProjectSumAllocationsDTO], Union[int, Decimal]): + grouped_allocations = self.segregate_allocations(payload) + result_allocations = [] total_plain_qf = 0 for project_address, project_allocations in grouped_allocations.items(): project_allocations = self._calc_allocations(project_allocations) diff --git a/backend/app/engine/projects/rewards/preliminary.py b/backend/app/engine/projects/rewards/preliminary.py index dd6fdc52b6..6f5c5866ba 100644 --- a/backend/app/engine/projects/rewards/preliminary.py +++ b/backend/app/engine/projects/rewards/preliminary.py @@ -1,20 +1,23 @@ from dataclasses import field, dataclass from decimal import Decimal -from typing import List +from typing import List, Dict from app.engine.projects.rewards import ( ProjectRewardsPayload, ProjectRewardsResult, ProjectRewards, ProjectRewardDTO, + AllocationsBelowThreshold, ) from app.engine.projects.rewards.allocations import ( ProjectAllocations, ProjectAllocationsPayload, + AllocationItem, ) from app.engine.projects.rewards.allocations.preliminary import ( PreliminaryProjectAllocations, ) +from app.engine.projects.rewards.leverage.preliminary import PreliminaryLeverage from app.engine.projects.rewards.threshold import ( ProjectThreshold, ProjectThresholdPayload, @@ -22,7 +25,6 @@ from app.engine.projects.rewards.threshold.preliminary import ( PreliminaryProjectThreshold, ) -from app.engine.projects.rewards.leverage.preliminary import PreliminaryLeverage @dataclass @@ -42,6 +44,13 @@ def calculate_threshold(self, total_allocated: int, projects: List[str]) -> int: ) ) + def get_total_allocations_below_threshold( + self, allocations: Dict[str, List[AllocationItem]] + ) -> AllocationsBelowThreshold: + return self.projects_threshold.get_total_allocations_below_threshold( + allocations + ) + def calculate_project_rewards( self, payload: ProjectRewardsPayload ) -> ProjectRewardsResult: diff --git a/backend/app/engine/projects/rewards/threshold/__init__.py b/backend/app/engine/projects/rewards/threshold/__init__.py index 4295633921..0e9d4ada16 100644 --- a/backend/app/engine/projects/rewards/threshold/__init__.py +++ b/backend/app/engine/projects/rewards/threshold/__init__.py @@ -1,5 +1,9 @@ from abc import ABC, abstractmethod from dataclasses import dataclass +from typing import List, Dict + +from app.engine.projects.rewards import AllocationsBelowThreshold +from app.engine.projects.rewards import AllocationItem @dataclass @@ -13,3 +17,9 @@ class ProjectThreshold(ABC): @abstractmethod def calculate_threshold(self, payload: ProjectThresholdPayload) -> int: pass + + @abstractmethod + def get_total_allocations_below_threshold( + self, allocations: Dict[str, List[AllocationItem]] + ) -> AllocationsBelowThreshold: + pass diff --git a/backend/app/engine/projects/rewards/threshold/preliminary.py b/backend/app/engine/projects/rewards/threshold/preliminary.py index 2c68c221da..534ae6deb0 100644 --- a/backend/app/engine/projects/rewards/threshold/preliminary.py +++ b/backend/app/engine/projects/rewards/threshold/preliminary.py @@ -1,9 +1,12 @@ from dataclasses import dataclass +from typing import List, Dict +from app.engine.projects.rewards import AllocationsBelowThreshold from app.engine.projects.rewards.threshold import ( ProjectThreshold, ProjectThresholdPayload, ) +from app.engine.projects.rewards import AllocationItem @dataclass @@ -19,3 +22,24 @@ def calculate_threshold(self, payload: ProjectThresholdPayload) -> int: if payload.projects_count else 0 ) + + def get_total_allocations_below_threshold( + self, allocations: Dict[str, List[AllocationItem]] + ) -> AllocationsBelowThreshold: + summed_allocations = { + project: sum(map(lambda value: int(value.amount), values)) + for project, values in allocations.items() + } + total_allocations = sum(summed_allocations.values()) + no_projects = len(allocations.keys()) + + threshold = self.calculate_threshold( + ProjectThresholdPayload(total_allocations, no_projects) + ) + + allocations_below_threshold = 0 + for allocations_sum_for_project in summed_allocations.values(): + if allocations_sum_for_project < threshold: + allocations_below_threshold += allocations_sum_for_project + + return AllocationsBelowThreshold(allocations_below_threshold, total_allocations) diff --git a/backend/app/infrastructure/routes/epochs.py b/backend/app/infrastructure/routes/epochs.py index 5c1968b12a..5f148331a7 100644 --- a/backend/app/infrastructure/routes/epochs.py +++ b/backend/app/infrastructure/routes/epochs.py @@ -106,6 +106,10 @@ def get(self): required=False, description="Community fund for the given epoch. It's calculated from staking proceeds directly.", ), + "donatedToProjects": fields.String( + required=False, + description="The amount of funds donated to projects. Includes MR and allocations.", + ), }, ) diff --git a/backend/app/modules/dto.py b/backend/app/modules/dto.py index 0bdbcf87c2..31247420be 100644 --- a/backend/app/modules/dto.py +++ b/backend/app/modules/dto.py @@ -53,6 +53,8 @@ class OctantRewardsDTO(JSONWizard): # Data available starting from Epoch 3 ppf: Optional[int] = None community_fund: Optional[int] = None + # Added after moving metrics from FE to BE + donated_to_projects: Optional[int] = None @dataclass(frozen=True) diff --git a/backend/app/modules/octant_rewards/general/service/finalized.py b/backend/app/modules/octant_rewards/general/service/finalized.py index ddb3bda5e0..2edfb3d85e 100644 --- a/backend/app/modules/octant_rewards/general/service/finalized.py +++ b/backend/app/modules/octant_rewards/general/service/finalized.py @@ -4,6 +4,8 @@ from app.infrastructure import database from app.modules.dto import OctantRewardsDTO from app.pydantic import Model +from app.engine.octant_rewards.leftover import LeftoverPayload +from app.engine.projects.rewards.allocations import ProjectAllocationsPayload class FinalizedOctantRewards(Model): @@ -15,6 +17,37 @@ def get_octant_rewards(self, context: Context) -> OctantRewardsDTO: context.epoch_details.epoch_num ) + leftover_payload = LeftoverPayload( + staking_proceeds=int(pending_snapshot.eth_proceeds), + operational_cost=int(pending_snapshot.operational_cost), + community_fund=pending_snapshot.validated_community_fund, + ppf=pending_snapshot.validated_ppf, + total_withdrawals=int(finalized_snapshot.total_withdrawals), + ) + + unused_matched_rewards = context.epoch_settings.octant_rewards.leftover.extract_unused_matched_rewards( + int(finalized_snapshot.leftover), leftover_payload + ) + + allocations_for_epoch = database.allocations.get_all_by_epoch( + context.epoch_details.epoch_num + ) + grouped_allocations = context.epoch_settings.project.rewards.projects_allocations.segregate_allocations( + ProjectAllocationsPayload(allocations=allocations_for_epoch) + ) + allocations_result = context.epoch_settings.project.rewards.get_total_allocations_below_threshold( + grouped_allocations + ) + allocations_sum = allocations_result.total + allocations_below_threshold = allocations_result.below_threshold + + donated_to_projects = ( + int(finalized_snapshot.matched_rewards) + - unused_matched_rewards + + allocations_sum + - allocations_below_threshold + ) + return OctantRewardsDTO( staking_proceeds=int(pending_snapshot.eth_proceeds), locked_ratio=Decimal(pending_snapshot.locked_ratio), @@ -28,6 +61,7 @@ def get_octant_rewards(self, context: Context) -> OctantRewardsDTO: total_withdrawals=int(finalized_snapshot.total_withdrawals), ppf=pending_snapshot.validated_ppf, community_fund=pending_snapshot.validated_community_fund, + donated_to_projects=donated_to_projects, ) def get_leverage(self, context: Context) -> float: diff --git a/backend/app/modules/octant_rewards/general/service/pending.py b/backend/app/modules/octant_rewards/general/service/pending.py index 16717819ad..52a13f54ad 100644 --- a/backend/app/modules/octant_rewards/general/service/pending.py +++ b/backend/app/modules/octant_rewards/general/service/pending.py @@ -27,8 +27,8 @@ class ProjectRewards(Protocol): def get_finalized_project_rewards( self, context: Context, - allocations: list[AllocationDTO], - all_projects: list[str], + allocations: List[AllocationDTO], + all_projects: List[str], matched_rewards: int, ) -> FinalizedProjectRewards: ... @@ -51,6 +51,7 @@ def get_octant_rewards(self, context: Context) -> OctantRewardsDTO: context.epoch_details.epoch_num ) matched_rewards = self.octant_matched_rewards.get_matched_rewards(context) + project_rewards = self._get_project_rewards(context, matched_rewards) return OctantRewardsDTO( staking_proceeds=int(pending_snapshot.eth_proceeds), @@ -63,7 +64,10 @@ def get_octant_rewards(self, context: Context) -> OctantRewardsDTO: ppf=pending_snapshot.validated_ppf, matched_rewards=matched_rewards, patrons_rewards=self.patrons_mode.get_patrons_rewards(context), - leftover=self.get_leftover(context, pending_snapshot, matched_rewards), + leftover=self._get_leftover( + context, pending_snapshot, matched_rewards, project_rewards + ), + donated_to_projects=self._get_donated_to_projects(project_rewards), ) def get_matched_rewards(self, context: Context) -> int: @@ -79,19 +83,14 @@ def get_leverage(self, context: Context) -> float: matched_rewards, allocations_sum ) - def get_leftover( + def _get_leftover( self, context: Context, pending_snapshot: PendingEpochSnapshot, matched_rewards: int, + project_rewards: FinalizedProjectRewards, ) -> int: - allocations = database.allocations.get_all_with_uqs( - context.epoch_details.epoch_num - ) _, user_rewards = self.user_rewards.get_claimed_rewards(context) - project_rewards = self.project_rewards.get_finalized_project_rewards( - context, allocations, context.projects_details.projects, matched_rewards - ) return context.epoch_settings.octant_rewards.leftover.calculate_leftover( LeftoverPayload( @@ -106,3 +105,19 @@ def get_leftover( used_matched_rewards=sum(r.matched for r in project_rewards.rewards), ) ) + + def _get_donated_to_projects(self, project_rewards: FinalizedProjectRewards) -> int: + total_user_donations_with_used_matched_rewards = sum( + r.amount for r in project_rewards.rewards + ) + + return total_user_donations_with_used_matched_rewards + + def _get_project_rewards(self, context: Context, matched_rewards: int): + allocations = database.allocations.get_all_with_uqs( + context.epoch_details.epoch_num + ) + project_rewards = self.project_rewards.get_finalized_project_rewards( + context, allocations, context.projects_details.projects, matched_rewards + ) + return project_rewards diff --git a/backend/app/modules/projects/rewards/service/finalizing.py b/backend/app/modules/projects/rewards/service/finalizing.py index 726256fb6c..90a9331f16 100644 --- a/backend/app/modules/projects/rewards/service/finalizing.py +++ b/backend/app/modules/projects/rewards/service/finalizing.py @@ -1,3 +1,5 @@ +from typing import List + from app.context.manager import Context from app.modules.common.project_rewards import get_projects_rewards, AllocationsPayload from app.modules.dto import AllocationDTO, ProjectAccountFundsDTO @@ -9,8 +11,8 @@ class FinalizingProjectRewards(Model): def get_finalized_project_rewards( self, context: Context, - allocations: list[AllocationDTO], - all_projects: list[str], + allocations: List[AllocationDTO], + all_projects: List[str], matched_rewards: int, ) -> FinalizedProjectRewards: allocations_payload = AllocationsPayload( diff --git a/backend/tests/conftest.py b/backend/tests/conftest.py index 52d1da7180..d1a9bcc027 100644 --- a/backend/tests/conftest.py +++ b/backend/tests/conftest.py @@ -6,16 +6,16 @@ import logging import os import time -import urllib.request import urllib.error +import urllib.request from unittest.mock import MagicMock, Mock import gql -from gql.transport.exceptions import TransportQueryError import pytest from flask import current_app from flask import g as request_context from flask.testing import FlaskClient +from gql.transport.exceptions import TransportQueryError from requests import RequestException from web3 import Web3 @@ -23,8 +23,8 @@ from app.engine.user.effective_deposit import DepositEvent, EventType, UserDeposit from app.exceptions import ExternalApiException from app.extensions import db, deposits, glm, gql_factory, w3, vault, epochs -from app.infrastructure import database from app.infrastructure import Client as GQLClient +from app.infrastructure import database from app.infrastructure.contracts.epochs import Epochs from app.infrastructure.contracts.erc20 import ERC20 from app.infrastructure.contracts.projects import Projects @@ -1297,10 +1297,14 @@ def mock_users_db(app, user_accounts): @pytest.fixture(scope="function") def mock_pending_epoch_snapshot_db_since_epoch3( - app, mock_users_db, ppf=PPF, cf=COMMUNITY_FUND + app, + mock_users_db, + ppf=PPF, + cf=COMMUNITY_FUND, + epoch=MOCKED_EPOCH_NO_AFTER_OVERHAUL, ): create_pending_snapshot( - epoch_nr=MOCKED_EPOCH_NO_AFTER_OVERHAUL, + epoch_nr=epoch, mock_users_db=mock_users_db, optional_ppf=ppf, optional_cf=cf, @@ -1328,22 +1332,9 @@ def mock_finalized_epoch_snapshot_db_since_epoch3(app, user_accounts): @pytest.fixture(scope="function") -def mock_finalized_epoch_snapshot_db(app, user_accounts): - database.finalized_epoch_snapshot.save_snapshot( - MOCKED_FINALIZED_EPOCH_NO, - MATCHED_REWARDS, - NO_PATRONS_REWARDS, - LEFTOVER, - total_withdrawals=TOTAL_WITHDRAWALS, - ) - - db.session.commit() - - -@pytest.fixture(scope="function") -def mock_allocations_db(app, mock_users_db, project_accounts): - prev_epoch_context = get_context(MOCKED_PENDING_EPOCH_NO - 1) - pending_epoch_context = get_context(MOCKED_PENDING_EPOCH_NO) +def mock_allocations_db(mock_users_db, project_accounts, epoch=MOCKED_PENDING_EPOCH_NO): + prev_epoch_context = get_context(epoch - 1) + pending_epoch_context = get_context(epoch) user1, user2, _ = mock_users_db user1_allocations = [ diff --git a/backend/tests/helpers/constants.py b/backend/tests/helpers/constants.py index b16ea4dfd9..ea05c7d4ea 100644 --- a/backend/tests/helpers/constants.py +++ b/backend/tests/helpers/constants.py @@ -7,6 +7,7 @@ MOCKED_PENDING_EPOCH_NO = 1 MOCKED_FINALIZED_EPOCH_NO = 1 MOCKED_EPOCH_NO_AFTER_OVERHAUL = 3 +MOCKED_EPOCH_NO_WITH_CAPPED_MR = 4 MOCKED_CURRENT_EPOCH_NO = 2 NO_PATRONS_REWARDS = 0 ETH_PROCEEDS = 402_410958904_110000000 @@ -31,6 +32,13 @@ ETH_PROCEEDS, LOCKED_RATIO, USER2_BUDGET ) COMMUNITY_FUND = int(Decimal("0.05") * ETH_PROCEEDS) +LEFTOVER_WITH_PPF_UNUSED_MR = ( + ETH_PROCEEDS + - OPERATIONAL_COST + - COMMUNITY_FUND + - int(0.5 * PPF) + - TOTAL_WITHDRAWALS +) USER1_ADDRESS = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" USER2_ADDRESS = "0x70997970C51812dc3A010C7d01b50e0d17dc79C8" diff --git a/backend/tests/helpers/finalized_snapshots.py b/backend/tests/helpers/finalized_snapshots.py new file mode 100644 index 0000000000..aee99bc3e3 --- /dev/null +++ b/backend/tests/helpers/finalized_snapshots.py @@ -0,0 +1,18 @@ +from app.extensions import db +from app.infrastructure import database +from tests.helpers.constants import ( + NO_PATRONS_REWARDS, + TOTAL_WITHDRAWALS, +) + + +def create_finalized_snapshot(epoch_nr: int, matched_rewards: int, leftover: int): + database.finalized_epoch_snapshot.save_snapshot( + epoch_nr, + matched_rewards, + NO_PATRONS_REWARDS, + leftover, + total_withdrawals=TOTAL_WITHDRAWALS, + ) + + db.session.commit() diff --git a/backend/tests/modules/octant_rewards/conftest.py b/backend/tests/modules/octant_rewards/conftest.py new file mode 100644 index 0000000000..8895ce9fe2 --- /dev/null +++ b/backend/tests/modules/octant_rewards/conftest.py @@ -0,0 +1,42 @@ +import pytest + +from tests.helpers.constants import ( + MOCKED_EPOCH_NO_WITH_CAPPED_MR, + COMMUNITY_FUND, + PPF, + MOCKED_FINALIZED_EPOCH_NO, + MATCHED_REWARDS_AFTER_OVERHAUL, + MATCHED_REWARDS, + LEFTOVER, + LEFTOVER_WITH_PPF_UNUSED_MR, +) +from tests.helpers.finalized_snapshots import create_finalized_snapshot +from tests.helpers.pending_snapshot import create_pending_snapshot + + +@pytest.fixture(scope="function") +def mock_pending_epoch_snapshot_with_uq_scores( + mock_users_db_with_scores, ppf=PPF, cf=COMMUNITY_FUND +): + user1_, user2, user3 = mock_users_db_with_scores + create_pending_snapshot( + epoch_nr=MOCKED_EPOCH_NO_WITH_CAPPED_MR, + optional_ppf=ppf, + optional_cf=cf, + mock_users_db=mock_users_db_with_scores, + ) + return user1_, user2, user3 + + +@pytest.fixture(scope="function") +def mock_finalized_epoch_snapshot_db(app, user_accounts): + create_finalized_snapshot(MOCKED_FINALIZED_EPOCH_NO, MATCHED_REWARDS, LEFTOVER) + + +@pytest.fixture(scope="function") +def mock_finalized_epoch_snapshot_db_for_e4(app, user_accounts): + create_finalized_snapshot( + MOCKED_EPOCH_NO_WITH_CAPPED_MR, + MATCHED_REWARDS_AFTER_OVERHAUL, + LEFTOVER_WITH_PPF_UNUSED_MR, + ) diff --git a/backend/tests/modules/octant_rewards/helpers/checker.py b/backend/tests/modules/octant_rewards/helpers/checker.py index 28450dea6b..d090fbec5e 100644 --- a/backend/tests/modules/octant_rewards/helpers/checker.py +++ b/backend/tests/modules/octant_rewards/helpers/checker.py @@ -16,6 +16,7 @@ def check_octant_rewards( matched_rewards: int = None, total_withdrawals: int = None, patrons_rewards: int = None, + donated_to_projects: int = None, ): assert rewards.staking_proceeds == ETH_PROCEEDS assert rewards.locked_ratio == LOCKED_RATIO @@ -29,3 +30,4 @@ def check_octant_rewards( assert rewards.leftover == leftover assert rewards.community_fund == community_fund assert rewards.ppf == ppf + assert rewards.donated_to_projects == donated_to_projects diff --git a/backend/tests/modules/octant_rewards/test_calculated_octant_rewards.py b/backend/tests/modules/octant_rewards/test_calculated_octant_rewards.py index 0a174fbdcc..69200be707 100644 --- a/backend/tests/modules/octant_rewards/test_calculated_octant_rewards.py +++ b/backend/tests/modules/octant_rewards/test_calculated_octant_rewards.py @@ -41,6 +41,7 @@ def test_calculate_octant_rewards_before_overhaul( assert result.total_rewards == expected_tr assert result.vanilla_individual_rewards == expected_ir assert result.operational_cost == expected_operational_cost + assert result.donated_to_projects is None def test_calculate_octant_rewards_after_overhaul( @@ -69,3 +70,4 @@ def test_calculate_octant_rewards_after_overhaul( assert result.ppf == overhaul_formulas.ppf( result.staking_proceeds, result.vanilla_individual_rewards, LOCKED_RATIO ) + assert result.donated_to_projects is None diff --git a/backend/tests/modules/octant_rewards/test_finalized_octant_rewards.py b/backend/tests/modules/octant_rewards/test_finalized_octant_rewards.py index c1b48079f2..2b4a94690b 100644 --- a/backend/tests/modules/octant_rewards/test_finalized_octant_rewards.py +++ b/backend/tests/modules/octant_rewards/test_finalized_octant_rewards.py @@ -31,6 +31,7 @@ def test_finalized_octant_rewards_before_overhaul( matched_rewards=MATCHED_REWARDS, total_withdrawals=TOTAL_WITHDRAWALS, patrons_rewards=NO_PATRONS_REWARDS, + donated_to_projects=MATCHED_REWARDS, ) @@ -51,6 +52,7 @@ def test_finalized_octant_rewards_after_overhaul( matched_rewards=MATCHED_REWARDS_AFTER_OVERHAUL, total_withdrawals=TOTAL_WITHDRAWALS, patrons_rewards=NO_PATRONS_REWARDS, + donated_to_projects=MATCHED_REWARDS_AFTER_OVERHAUL, ) @@ -70,3 +72,13 @@ def test_finalized_get_leverage( result = service.get_leverage(context) assert result == 144160.63189897747 + + +def test_donated_to_projects_in_octant_rewards_for_capped_mr( + mock_pending_epoch_snapshot_with_uq_scores, mock_finalized_epoch_snapshot_db_for_e4 +): + context = get_context(epoch_num=4) + service = FinalizedOctantRewards() + result = service.get_octant_rewards(context) + + assert result.donated_to_projects == 140849434135859019815 diff --git a/backend/tests/modules/octant_rewards/test_pending_octant_rewards.py b/backend/tests/modules/octant_rewards/test_pending_octant_rewards.py index 2f2e2c5ee2..f0aec7ccce 100644 --- a/backend/tests/modules/octant_rewards/test_pending_octant_rewards.py +++ b/backend/tests/modules/octant_rewards/test_pending_octant_rewards.py @@ -4,6 +4,7 @@ from app.modules.octant_rewards.general.service.pending import PendingOctantRewards from app.modules.octant_rewards.matched.pending import PendingOctantMatchedRewards from app.modules.projects.rewards.service.finalizing import FinalizingProjectRewards +from tests.helpers import make_user_allocation from tests.helpers.constants import ( USER1_BUDGET, MOCKED_EPOCH_NO_AFTER_OVERHAUL, @@ -13,7 +14,6 @@ MATCHED_REWARDS, MATCHED_REWARDS_AFTER_OVERHAUL, ) -from tests.helpers import make_user_allocation from tests.helpers.context import get_context from tests.helpers.pending_snapshot import create_pending_snapshot from tests.modules.octant_rewards.helpers.checker import check_octant_rewards @@ -43,6 +43,7 @@ def test_pending_octant_rewards_before_overhaul( patrons_rewards=USER2_BUDGET, matched_rewards=MATCHED_REWARDS + USER2_BUDGET, leftover=321928766823288000000, + donated_to_projects=0, ) @@ -60,6 +61,7 @@ def test_pending_octant_rewards_after_overhaul( patrons_rewards=USER2_BUDGET, matched_rewards=MATCHED_REWARDS_AFTER_OVERHAUL, leftover=282293485473756640672, + donated_to_projects=0, ) @@ -117,3 +119,28 @@ def test_pending_get_leverage( result = service.get_leverage(context) assert result == 144164.29856550877 + + +def test_donated_to_projects_in_octant_rewards_for_capped_mr( + mock_pending_epoch_snapshot_with_uq_scores, service +): + user1, _, _ = mock_pending_epoch_snapshot_with_uq_scores + context = get_context(epoch_num=4) + make_user_allocation( + context, + user1, + allocation_items=[ + AllocationItem(context.projects_details.projects[0], USER1_BUDGET) + ], + ) + result = service.get_octant_rewards(context) + + check_octant_rewards( + result, + community_fund=COMMUNITY_FUND, + ppf=PPF, + patrons_rewards=USER2_BUDGET, + matched_rewards=MATCHED_REWARDS_AFTER_OVERHAUL, + leftover=366801619086282814574, + donated_to_projects=28171413696161041950, + ) diff --git a/backend/tests/modules/snapshots/finalized/test_finalizing_snapshots.py b/backend/tests/modules/snapshots/finalized/test_finalizing_snapshots.py index 9129dd996f..71b26b59df 100644 --- a/backend/tests/modules/snapshots/finalized/test_finalizing_snapshots.py +++ b/backend/tests/modules/snapshots/finalized/test_finalizing_snapshots.py @@ -8,7 +8,11 @@ from app.modules.snapshots.finalized.service.finalizing import FinalizingSnapshots from tests.helpers import make_user_allocation from tests.helpers.allocations import make_user_allocation_with_uq_score -from tests.helpers.constants import MATCHED_REWARDS, USER2_BUDGET, LOW_UQ_SCORE +from tests.helpers.constants import ( + MATCHED_REWARDS, + USER2_BUDGET, + LOW_UQ_SCORE, +) from tests.helpers.context import get_context diff --git a/client/src/api/calls/epochInfo.ts b/client/src/api/calls/epochInfo.ts index ee377d6533..84ae716cec 100644 --- a/client/src/api/calls/epochInfo.ts +++ b/client/src/api/calls/epochInfo.ts @@ -5,6 +5,7 @@ import apiService from 'services/apiService'; export type Response = { communityFund: string | null; + donatedToProjects: string | null; leftover: string | null; matchedRewards: string | null; operationalCost: string; diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpoch.tsx b/client/src/components/Metrics/MetricsEpoch/MetricsEpoch.tsx index 1f3358473e..54e71b2136 100644 --- a/client/src/components/Metrics/MetricsEpoch/MetricsEpoch.tsx +++ b/client/src/components/Metrics/MetricsEpoch/MetricsEpoch.tsx @@ -127,7 +127,6 @@ const MetricsEpoch = (): ReactElement => { /> = ({ className, totalUserDonationsWithPatronRewards, unusedRewards, - ethBelowThreshold, }) => { const { t } = useTranslation('translation', { keyPrefix: 'views.metrics' }); const { epoch } = useMetricsEpoch(); @@ -30,16 +29,11 @@ const MetricsEpochGridFundsUsage: FC = ({ const leftover = epochInfo ? epochInfo.leftover : BigInt(0); const projectCosts = epochInfo ? epochInfo.operationalCost : BigInt(0); + const donatedToProjects = epochInfo ? epochInfo.donatedToProjects : BigInt(0); const staking = epochInfo ? epochInfo.staking : BigInt(0); const ppf = epochInfo ? epochInfo.ppf : BigInt(0); const communityFund = epochInfo ? epochInfo.communityFund : BigInt(0); - const donatedToProjects = epochInfo - ? epochInfo.matchedRewards + - (totalUserDonationsWithPatronRewards - epochInfo.patronsRewards) - - ethBelowThreshold - : BigInt(0); - const claimedByUsers = useMemo(() => { if (!epochInfo) { return BigInt(0); diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridFundsUsage/types.ts b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridFundsUsage/types.ts index b52432b763..fe3f173335 100644 --- a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridFundsUsage/types.ts +++ b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridFundsUsage/types.ts @@ -1,6 +1,5 @@ export default interface MetricsEpochGridFundsUsageProps { className?: string; - ethBelowThreshold: bigint; isLoading: boolean; totalUserDonationsWithPatronRewards: bigint; unusedRewards: bigint; diff --git a/client/src/hooks/queries/useEpochInfo.ts b/client/src/hooks/queries/useEpochInfo.ts index b9f78719dc..9f6a255477 100644 --- a/client/src/hooks/queries/useEpochInfo.ts +++ b/client/src/hooks/queries/useEpochInfo.ts @@ -7,6 +7,7 @@ import { parseUnitsBigInt } from 'utils/parseUnitsBigInt'; type EpochInfo = { communityFund: bigint; + donatedToProjects: bigint; leftover: bigint; matchedRewards: bigint; operationalCost: bigint; @@ -49,6 +50,9 @@ export default function useEpochInfo( communityFund: response.communityFund ? parseUnitsBigInt(response.communityFund, 'wei') : BigInt(0), + donatedToProjects: response.donatedToProjects + ? parseUnitsBigInt(response.donatedToProjects, 'wei') + : BigInt(0), matchedRewards: response.matchedRewards ? parseUnitsBigInt(response.matchedRewards, 'wei') : BigInt(0), From 143eabdc0b949bcb0a39f2a53cfb34d251a13b3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Garbaci=C5=84ski?= <57113816+kgarbacinski@users.noreply.github.com> Date: Mon, 2 Sep 2024 14:36:10 +0200 Subject: [PATCH 055/241] OCT-1861: Cover /recalculate with API Tests (#394) ## Description ## Definition of Done 1. [ ] Acceptance criteria are met. 2. [ ] PR is manually tested before the merge by developer(s). - [ ] Happy path is manually checked. 3. [ ] PR is manually tested by QA when their assistance is required (1). - [ ] Octant Areas & Test Cases are checked for impact and updated if required (2). 4. [ ] Unit tests are added unless there is a reason to omit them. 5. [ ] Automated tests are added when required. 6. [ ] The code is merged. 7. [ ] Tech documentation is added / updated, reviewed and approved (including mandatory approval by a code owner, should such exist for changed files). - [ ] BE: Swagger documentation is updated. 8. [ ] When required by QA: - [ ] Deployed to the relevant environment. - [ ] Passed system tests. --- (1) Developer(s) in coordination with QA decide whether it's required. For small tickets introducing small changes QA assistance is most probably not required. (2) [Octant Areas & Test Cases](https://docs.google.com/spreadsheets/d/1cRe6dxuKJV3a4ZskAwWEPvrFkQm6rEfyUCYwLTYw_Cc). --- backend/tests/api-e2e/test_api_delegation.py | 60 ++++++++++++++++++++ backend/tests/conftest.py | 18 ++++++ 2 files changed, 78 insertions(+) diff --git a/backend/tests/api-e2e/test_api_delegation.py b/backend/tests/api-e2e/test_api_delegation.py index 8722656b86..cf6bfdcc04 100644 --- a/backend/tests/api-e2e/test_api_delegation.py +++ b/backend/tests/api-e2e/test_api_delegation.py @@ -57,6 +57,66 @@ def test_delegation(client: Client, payload: ScoreDelegationPayload): assert delegatee_score["status"] == "Known" assert float(delegatee_score["score"]) == float(delegator_score["score"]) + # check if the secondary address is actually used off + resp, code = client.delegate( + primary_address=payload.primary_addr, + secondary_address=payload.secondary_addr, + primary_address_signature=payload.primary_addr_signature, + secondary_address_signature=payload.secondary_addr_signature, + ) + + assert code == 400 + assert resp["message"] == "Delegation already exists" + + +@pytest.mark.api +def test_recalculate_in_delegation(client: Client, payload: ScoreDelegationPayload): + """ + Recalculation can actually return two different results: + - if the delegation does not exist, it will return 400 + - if the delegation exists, i.e. secondary address exists in the database, it will return 400 + it's due to the fact that the recalculation is already stoned for a secondary address in our implementation + """ + client.move_to_next_epoch(STARTING_EPOCH + 1) + client.move_to_next_epoch(STARTING_EPOCH + 2) + client.move_to_next_epoch(STARTING_EPOCH + 3) + + epoch_no = client.wait_for_sync(STARTING_EPOCH + 3) + app.logger.debug(f"indexed epoch: {epoch_no}") + + database.user.add_user(USER1_ADDRESS) + database.user.add_user(USER2_ADDRESS) + + # try to recalculate before delegation + data, status = client.delegation_recalculate( + primary_address=payload.primary_addr, + secondary_address=payload.secondary_addr, + primary_address_signature=payload.primary_addr_signature, + secondary_address_signature=payload.secondary_addr_signature, + ) + assert data["message"] == "Delegation does not exists" + assert status == 400 + + # make a delegation + _, status = client.delegate( + primary_address=payload.primary_addr, + secondary_address=payload.secondary_addr, + primary_address_signature=payload.primary_addr_signature, + secondary_address_signature=payload.secondary_addr_signature, + ) + assert status == 201 + + # recalculate after delegation + data, status = client.delegation_recalculate( + primary_address=payload.primary_addr, + secondary_address=payload.secondary_addr, + primary_address_signature=payload.primary_addr_signature, + secondary_address_signature=payload.secondary_addr_signature, + ) + + assert data["message"] == "Invalid recalculation request" + assert status == 400 + @pytest.mark.api def test_check_delegation(client: Client, payload: ScoreDelegationPayload): diff --git a/backend/tests/conftest.py b/backend/tests/conftest.py index d1a9bcc027..a6533926ce 100644 --- a/backend/tests/conftest.py +++ b/backend/tests/conftest.py @@ -957,6 +957,24 @@ def delegate( ) return json.loads(rv.text), rv.status_code + def delegation_recalculate( + self, + primary_address: str, + secondary_address: str, + primary_address_signature: str, + secondary_address_signature: str, + ) -> tuple[dict, int]: + rv = self._flask_client.put( + "/delegation/recalculate", + json={ + "primaryAddr": primary_address, + "secondaryAddr": secondary_address, + "primaryAddrSignature": primary_address_signature, + "secondaryAddrSignature": secondary_address_signature, + }, + ) + return json.loads(rv.text), rv.status_code + @property def config(self): return self._flask_client.application.config From a7aa142ded623702fc7ff88bcaa17336fc358197 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Miko=C5=82ajczyk?= Date: Mon, 2 Sep 2024 16:28:19 +0200 Subject: [PATCH 056/241] oct-1876: home transactions --- .../src/components/Home/HomeGrid/HomeGrid.tsx | 11 +- .../HomeGridTransactions.module.scss | 16 ++ .../HomeGridTransactions.tsx | 65 ++++++++ .../ModalTransactionDetails.tsx | 53 ++++++ .../TransactionDetails/TransactionDetails.tsx | 16 ++ .../TransactionDetailsAllocation.module.scss | 22 +++ .../TransactionDetailsAllocation.tsx | 152 ++++++++++++++++++ .../TransactionDetailsAllocation/index.tsx | 2 + .../TransactionDetailsAllocation/types.ts | 8 + .../TransactionDetailsDateAndTime.module.scss | 3 + .../TransactionDetailsDateAndTime.tsx | 11 ++ .../TransactionDetailsDateAndTime/index.tsx | 2 + .../TransactionDetailsDateAndTime/types.ts | 3 + .../TransactionDetailsDateAndTime/utils.ts | 6 + .../TransactionDetailsRest.module.scss | 8 + .../TransactionDetailsRest.tsx | 90 +++++++++++ .../TransactionDetailsRest/index.tsx | 2 + .../TransactionDetailsRest/types.ts | 5 + .../TransactionDetails/index.tsx | 2 + .../TransactionDetails/types.ts | 5 + .../ModalTransactionDetails/index.tsx | 2 + .../ModalTransactionDetails/types.ts | 6 + .../TransactionLabel.module.scss | 17 ++ .../TransactionLabel/TransactionLabel.tsx | 27 ++++ .../TransactionLabel/index.tsx | 2 + .../TransactionLabel/types.ts | 4 + .../TransactionsList.module.scss | 10 ++ .../TransactionsList/TransactionsList.tsx | 28 ++++ .../TransactionsList/index.tsx | 2 + .../TransactionsList/types.ts | 5 + .../TransactionsListItem.module.scss | 57 +++++++ .../TransactionsListItem.tsx | 102 ++++++++++++ .../TransactionsListItem/index.tsx | 2 + .../TransactionsListItem/types.ts | 12 ++ .../TransactionsSkeleton.module.scss | 30 ++++ .../TransactionsSkeleton.tsx | 21 +++ .../TransactionsSkeleton/index.tsx | 2 + .../Home/HomeGridTransactions/index.tsx | 2 + .../Home/HomeGridTransactions/types.ts | 3 + .../ProjectsListSkeletonItem.module.scss | 4 - client/src/locales/en/translation.json | 106 ++++++------ 41 files changed, 865 insertions(+), 61 deletions(-) create mode 100644 client/src/components/Home/HomeGridTransactions/HomeGridTransactions.module.scss create mode 100644 client/src/components/Home/HomeGridTransactions/HomeGridTransactions.tsx create mode 100644 client/src/components/Home/HomeGridTransactions/ModalTransactionDetails/ModalTransactionDetails.tsx create mode 100644 client/src/components/Home/HomeGridTransactions/ModalTransactionDetails/TransactionDetails/TransactionDetails.tsx create mode 100644 client/src/components/Home/HomeGridTransactions/ModalTransactionDetails/TransactionDetails/TransactionDetailsAllocation/TransactionDetailsAllocation.module.scss create mode 100644 client/src/components/Home/HomeGridTransactions/ModalTransactionDetails/TransactionDetails/TransactionDetailsAllocation/TransactionDetailsAllocation.tsx create mode 100644 client/src/components/Home/HomeGridTransactions/ModalTransactionDetails/TransactionDetails/TransactionDetailsAllocation/index.tsx create mode 100644 client/src/components/Home/HomeGridTransactions/ModalTransactionDetails/TransactionDetails/TransactionDetailsAllocation/types.ts create mode 100644 client/src/components/Home/HomeGridTransactions/ModalTransactionDetails/TransactionDetails/TransactionDetailsDateAndTime/TransactionDetailsDateAndTime.module.scss create mode 100644 client/src/components/Home/HomeGridTransactions/ModalTransactionDetails/TransactionDetails/TransactionDetailsDateAndTime/TransactionDetailsDateAndTime.tsx create mode 100644 client/src/components/Home/HomeGridTransactions/ModalTransactionDetails/TransactionDetails/TransactionDetailsDateAndTime/index.tsx create mode 100644 client/src/components/Home/HomeGridTransactions/ModalTransactionDetails/TransactionDetails/TransactionDetailsDateAndTime/types.ts create mode 100644 client/src/components/Home/HomeGridTransactions/ModalTransactionDetails/TransactionDetails/TransactionDetailsDateAndTime/utils.ts create mode 100644 client/src/components/Home/HomeGridTransactions/ModalTransactionDetails/TransactionDetails/TransactionDetailsRest/TransactionDetailsRest.module.scss create mode 100644 client/src/components/Home/HomeGridTransactions/ModalTransactionDetails/TransactionDetails/TransactionDetailsRest/TransactionDetailsRest.tsx create mode 100644 client/src/components/Home/HomeGridTransactions/ModalTransactionDetails/TransactionDetails/TransactionDetailsRest/index.tsx create mode 100644 client/src/components/Home/HomeGridTransactions/ModalTransactionDetails/TransactionDetails/TransactionDetailsRest/types.ts create mode 100644 client/src/components/Home/HomeGridTransactions/ModalTransactionDetails/TransactionDetails/index.tsx create mode 100644 client/src/components/Home/HomeGridTransactions/ModalTransactionDetails/TransactionDetails/types.ts create mode 100644 client/src/components/Home/HomeGridTransactions/ModalTransactionDetails/index.tsx create mode 100644 client/src/components/Home/HomeGridTransactions/ModalTransactionDetails/types.ts create mode 100644 client/src/components/Home/HomeGridTransactions/TransactionLabel/TransactionLabel.module.scss create mode 100644 client/src/components/Home/HomeGridTransactions/TransactionLabel/TransactionLabel.tsx create mode 100644 client/src/components/Home/HomeGridTransactions/TransactionLabel/index.tsx create mode 100644 client/src/components/Home/HomeGridTransactions/TransactionLabel/types.ts create mode 100644 client/src/components/Home/HomeGridTransactions/TransactionsList/TransactionsList.module.scss create mode 100644 client/src/components/Home/HomeGridTransactions/TransactionsList/TransactionsList.tsx create mode 100644 client/src/components/Home/HomeGridTransactions/TransactionsList/index.tsx create mode 100644 client/src/components/Home/HomeGridTransactions/TransactionsList/types.ts create mode 100644 client/src/components/Home/HomeGridTransactions/TransactionsListItem/TransactionsListItem.module.scss create mode 100644 client/src/components/Home/HomeGridTransactions/TransactionsListItem/TransactionsListItem.tsx create mode 100644 client/src/components/Home/HomeGridTransactions/TransactionsListItem/index.tsx create mode 100644 client/src/components/Home/HomeGridTransactions/TransactionsListItem/types.ts create mode 100644 client/src/components/Home/HomeGridTransactions/TransactionsSkeleton/TransactionsSkeleton.module.scss create mode 100644 client/src/components/Home/HomeGridTransactions/TransactionsSkeleton/TransactionsSkeleton.tsx create mode 100644 client/src/components/Home/HomeGridTransactions/TransactionsSkeleton/index.tsx create mode 100644 client/src/components/Home/HomeGridTransactions/index.tsx create mode 100644 client/src/components/Home/HomeGridTransactions/types.ts diff --git a/client/src/components/Home/HomeGrid/HomeGrid.tsx b/client/src/components/Home/HomeGrid/HomeGrid.tsx index 4dd2c7965c..61fd8cb553 100644 --- a/client/src/components/Home/HomeGrid/HomeGrid.tsx +++ b/client/src/components/Home/HomeGrid/HomeGrid.tsx @@ -1,15 +1,15 @@ import React, { memo, ReactNode } from 'react'; import { useAccount } from 'wagmi'; +import HomeGridCurrentGlmLock from 'components/Home/HomeGridCurrentGlmLock'; +import HomeGridPersonalAllocation from 'components/Home/HomeGridPersonalAllocation'; +import HomeGridRewardsEstimator from 'components/Home/HomeGridRewardsEstimator'; +import HomeGridTransactions from 'components/Home/HomeGridTransactions'; +import HomeGridUQScore from 'components/Home/HomeGridUQScore'; import Grid from 'components/shared/Grid'; import styles from './HomeGrid.module.scss'; -import HomeGridCurrentGlmLock from '../HomeGridCurrentGlmLock'; -import HomeGridPersonalAllocation from '../HomeGridPersonalAllocation'; -import HomeGridRewardsEstimator from '../HomeGridRewardsEstimator'; -import HomeGridUQScore from '../HomeGridUQScore'; - const HomeGrid = (): ReactNode => { const { isConnected } = useAccount(); @@ -19,6 +19,7 @@ const HomeGrid = (): ReactNode => { {isConnected && } + ); }; diff --git a/client/src/components/Home/HomeGridTransactions/HomeGridTransactions.module.scss b/client/src/components/Home/HomeGridTransactions/HomeGridTransactions.module.scss new file mode 100644 index 0000000000..5418683c18 --- /dev/null +++ b/client/src/components/Home/HomeGridTransactions/HomeGridTransactions.module.scss @@ -0,0 +1,16 @@ +.root { + padding: 0rem 2.4rem 2.4rem; + overflow: auto; + + .divider { + margin-top: 2rem; + width: 100%; + height: 0.1rem; + background-color: $color-octant-grey1; + } + + .withdrawEthButton { + margin-top: 2.4rem; + width: 100%; + } +} diff --git a/client/src/components/Home/HomeGridTransactions/HomeGridTransactions.tsx b/client/src/components/Home/HomeGridTransactions/HomeGridTransactions.tsx new file mode 100644 index 0000000000..5deff39066 --- /dev/null +++ b/client/src/components/Home/HomeGridTransactions/HomeGridTransactions.tsx @@ -0,0 +1,65 @@ +import React, { FC } from 'react'; +import { useTranslation } from 'react-i18next'; +import InfiniteScroll from 'react-infinite-scroller'; + +import TransactionsList from 'components/Home/HomeGridTransactions/TransactionsList'; +import TransactionsSkeleton from 'components/Home/HomeGridTransactions/TransactionsSkeleton'; +import GridTile from 'components/shared/Grid/GridTile'; +import useCurrentEpoch from 'hooks/queries/useCurrentEpoch'; +import useHistory from 'hooks/queries/useHistory'; +import useTransactionLocalStore from 'store/transactionLocal/store'; +import getIsPreLaunch from 'utils/getIsPreLaunch'; + +import styles from './HomeGridTransactions.module.scss'; +import HomeGridTransactionsProps from './types'; + +const HomeGridTransactions: FC = ({ className }) => { + const { t } = useTranslation('translation', { + keyPrefix: 'components.home.homeGridTransactions', + }); + const { transactionsPending } = useTransactionLocalStore(state => ({ + transactionsPending: state.data.transactionsPending, + })); + + const { data: currentEpoch } = useCurrentEpoch(); + const { fetchNextPage, history, hasNextPage, isFetching: isFetchingHistory } = useHistory(); + + const isPreLaunch = getIsPreLaunch(currentEpoch); + const showLoader = isFetchingHistory && !isPreLaunch && !history?.length; + + const transactionsPendingSorted = transactionsPending?.sort( + ({ timestamp: timestampA }, { timestamp: timestampB }) => { + if (timestampA < timestampB) { + return 1; + } + if (timestampA > timestampB) { + return -1; + } + return 0; + }, + ); + + return ( + +
+ {showLoader ? ( +
+ +
+ ) : ( + } + loadMore={fetchNextPage} + pageStart={0} + > + + + )} +
+
+ ); +}; + +export default HomeGridTransactions; diff --git a/client/src/components/Home/HomeGridTransactions/ModalTransactionDetails/ModalTransactionDetails.tsx b/client/src/components/Home/HomeGridTransactions/ModalTransactionDetails/ModalTransactionDetails.tsx new file mode 100644 index 0000000000..941a61ae2b --- /dev/null +++ b/client/src/components/Home/HomeGridTransactions/ModalTransactionDetails/ModalTransactionDetails.tsx @@ -0,0 +1,53 @@ +import React, { FC, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; + +import TransactionDetails from 'components/Home/HomeGridTransactions/ModalTransactionDetails/TransactionDetails'; +import Modal from 'components/ui/Modal'; +import useEpochTimestampHappenedIn from 'hooks/subgraph/useEpochTimestampHappenedIn'; + +import ModalTransactionDetailsProps from './types'; + +const ModalTransactionDetails: FC = ({ + modalProps, + type, + timestamp, + isWaitingForTransactionInitialized, + ...rest +}) => { + const { i18n, t } = useTranslation('translation', { + keyPrefix: 'components.home.homeGridTransactions.modalTransactionDetails', + }); + const { data: epochTimestampHappenedIn } = useEpochTimestampHappenedIn(timestamp); + + const header = useMemo(() => { + switch (type) { + case 'patron_mode_donation': + return i18n.t( + 'components.home.historyItem.homeGridTransactions.transactionsListItem.epochDonation', + { + epoch: epochTimestampHappenedIn, + }, + ); + case 'lock': + return t('header.lock'); + case 'unlock': + return t('header.unlock'); + case 'allocation': + return t('header.allocation', { + epoch: epochTimestampHappenedIn ? epochTimestampHappenedIn - 1 : '?', + }); + case 'withdrawal': + return t('header.withdrawal'); + default: + return ''; + } + }, [t, type, epochTimestampHappenedIn, i18n]); + + return ( + + + + ); +}; + +export default ModalTransactionDetails; diff --git a/client/src/components/Home/HomeGridTransactions/ModalTransactionDetails/TransactionDetails/TransactionDetails.tsx b/client/src/components/Home/HomeGridTransactions/ModalTransactionDetails/TransactionDetails/TransactionDetails.tsx new file mode 100644 index 0000000000..76c6373f48 --- /dev/null +++ b/client/src/components/Home/HomeGridTransactions/ModalTransactionDetails/TransactionDetails/TransactionDetails.tsx @@ -0,0 +1,16 @@ +import React, { FC } from 'react'; + +import TransactionDetailsAllocation from 'components/Home/HomeGridTransactions/ModalTransactionDetails/TransactionDetails/TransactionDetailsAllocation'; +import TransactionDetailsAllocationProps from 'components/Home/HomeGridTransactions/ModalTransactionDetails/TransactionDetails/TransactionDetailsAllocation/types'; +import TransactionDetailsRest from 'components/Home/HomeGridTransactions/ModalTransactionDetails/TransactionDetails/TransactionDetailsRest'; + +import TransactionDetailsProps from './types'; + +const TransactionDetails: FC = props => + props.type === 'allocation' ? ( + + ) : ( + + ); + +export default TransactionDetails; diff --git a/client/src/components/Home/HomeGridTransactions/ModalTransactionDetails/TransactionDetails/TransactionDetailsAllocation/TransactionDetailsAllocation.module.scss b/client/src/components/Home/HomeGridTransactions/ModalTransactionDetails/TransactionDetails/TransactionDetailsAllocation/TransactionDetailsAllocation.module.scss new file mode 100644 index 0000000000..a13ed0c91b --- /dev/null +++ b/client/src/components/Home/HomeGridTransactions/ModalTransactionDetails/TransactionDetails/TransactionDetailsAllocation/TransactionDetailsAllocation.module.scss @@ -0,0 +1,22 @@ +.projects { + display: flex; + flex-direction: column; + margin-top: 1.6rem; + font-size: $font-size-14; + + @media #{$tablet-down} { + font-size: $font-size-12; + } +} + +.leverage { + color: $color-octant-dark; + + &.isFetchingEpochLeverage { + @include skeleton(); + } +} + +.tooltip { + max-width: 25rem; +} diff --git a/client/src/components/Home/HomeGridTransactions/ModalTransactionDetails/TransactionDetails/TransactionDetailsAllocation/TransactionDetailsAllocation.tsx b/client/src/components/Home/HomeGridTransactions/ModalTransactionDetails/TransactionDetails/TransactionDetailsAllocation/TransactionDetailsAllocation.tsx new file mode 100644 index 0000000000..fa5cecf390 --- /dev/null +++ b/client/src/components/Home/HomeGridTransactions/ModalTransactionDetails/TransactionDetails/TransactionDetailsAllocation/TransactionDetailsAllocation.tsx @@ -0,0 +1,152 @@ +import cx from 'classnames'; +import React, { FC, Fragment } from 'react'; +import { useTranslation } from 'react-i18next'; + +import TransactionDetailsDateAndTime from 'components/Home/HomeGridTransactions/ModalTransactionDetails/TransactionDetails/TransactionDetailsDateAndTime'; +import ProjectAllocationDetailRow from 'components/shared/ProjectAllocationDetailRow'; +import BoxRounded from 'components/ui/BoxRounded'; +import Sections from 'components/ui/BoxRounded/Sections/Sections'; +import { SectionProps } from 'components/ui/BoxRounded/Sections/types'; +import useGetValuesToDisplay from 'hooks/helpers/useGetValuesToDisplay'; +import useCurrentEpoch from 'hooks/queries/useCurrentEpoch'; +import useEpochLeverage from 'hooks/queries/useEpochLeverage'; +import useIndividualReward from 'hooks/queries/useIndividualReward'; +import useIsDecisionWindowOpen from 'hooks/queries/useIsDecisionWindowOpen'; +import useEpochTimestampHappenedIn from 'hooks/subgraph/useEpochTimestampHappenedIn'; +import { CryptoCurrency } from 'types/cryptoCurrency'; + +import styles from './TransactionDetailsAllocation.module.scss'; +import TransactionDetailsAllocationProps from './types'; + +const TransactionDetailsAllocation: FC = ({ + eventData: { amount, allocations, leverage }, + timestamp, +}) => { + const { t } = useTranslation('translation', { + keyPrefix: 'components.home.homeGridTransactions.modalTransactionDetails', + }); + const { data: currentEpoch } = useCurrentEpoch(); + const { data: isDecisionWindowOpen } = useIsDecisionWindowOpen(); + const { data: epochTimestampHappenedIn, isFetching: isFetchingEpochTimestampHappenedIn } = + useEpochTimestampHappenedIn(timestamp); + + const getValuesToDisplay = useGetValuesToDisplay(); + + const allocationEpoch = epochTimestampHappenedIn ? epochTimestampHappenedIn - 1 : undefined; + + const { data: epochLeverage, isFetching: isFetchingEpochLeverage } = + useEpochLeverage(allocationEpoch); + + const { data: individualReward, isFetching: isFetchingIndividualReward } = + useIndividualReward(allocationEpoch); + + const isPersonalOnlyAllocation = amount === 0n; + + const isAllocationFromCurrentAW = currentEpoch + ? isDecisionWindowOpen && allocationEpoch === currentEpoch - 1 + : false; + + /** + * leverage in the event is a value from the moment event happened. + * When event happened in the already closed AW we show epochLeverage, as it's the final one. + */ + const leverageInt = leverage ? parseInt(leverage, 10) : 0; + const leverageBigInt = BigInt(leverageInt); + const epochLeverageNumber = epochLeverage ? Math.round(epochLeverage) : 0; + const epochLeverageBigInt = BigInt(epochLeverageNumber); + + const leverageToUse = isAllocationFromCurrentAW ? leverageBigInt : epochLeverageBigInt; + + const sections: SectionProps[] = [ + { + doubleValueProps: { + cryptoCurrency: 'ethereum', + isFetching: isFetchingEpochTimestampHappenedIn || isFetchingIndividualReward, + showCryptoSuffix: true, + valueCrypto: individualReward ? individualReward - amount : BigInt(0), + }, + label: t('sections.allocationPersonal'), + }, + ...(isPersonalOnlyAllocation + ? [] + : ([ + { + doubleValueProps: { + cryptoCurrency: 'ethereum' as CryptoCurrency, + showCryptoSuffix: true, + valueCrypto: amount, + }, + label: t('sections.allocationProjects', { projectsNumber: allocations.length }), + }, + isAllocationFromCurrentAW + ? { + childrenRight: ( +
+ {leverage === null ? t('sections.leverageUnknown') : `${leverageInt}x`} +
+ ), + label: t('sections.estimatedLeverage'), + tooltipProps: { + position: 'bottom-right', + text: + leverage === null + ? t('sections.allocationTooltips.leverageUnknown') + : t('sections.allocationTooltips.leverage'), + tooltipClassName: styles.tooltip, + }, + } + : { + childrenRight: ( +
+ { + getValuesToDisplay({ + cryptoCurrency: 'ethereum', + showCryptoSuffix: true, + valueCrypto: amount * leverageToUse, + }).primary + } +
+ ), + label: t('sections.finalMatchFunding'), + tooltipProps: { + position: 'bottom-right', + text: t('sections.allocationTooltips.finalMatchFunding'), + tooltipClassName: styles.tooltip, + }, + }, + ] as SectionProps[])), + { + childrenRight: , + label: t('sections.when'), + }, + ]; + + return ( + + + + + {!isPersonalOnlyAllocation && ( + + {allocations?.map(allocation => ( + + ))} + + )} + + ); +}; + +export default TransactionDetailsAllocation; diff --git a/client/src/components/Home/HomeGridTransactions/ModalTransactionDetails/TransactionDetails/TransactionDetailsAllocation/index.tsx b/client/src/components/Home/HomeGridTransactions/ModalTransactionDetails/TransactionDetails/TransactionDetailsAllocation/index.tsx new file mode 100644 index 0000000000..ecc817e66d --- /dev/null +++ b/client/src/components/Home/HomeGridTransactions/ModalTransactionDetails/TransactionDetails/TransactionDetailsAllocation/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './TransactionDetailsAllocation'; diff --git a/client/src/components/Home/HomeGridTransactions/ModalTransactionDetails/TransactionDetails/TransactionDetailsAllocation/types.ts b/client/src/components/Home/HomeGridTransactions/ModalTransactionDetails/TransactionDetails/TransactionDetailsAllocation/types.ts new file mode 100644 index 0000000000..625655826e --- /dev/null +++ b/client/src/components/Home/HomeGridTransactions/ModalTransactionDetails/TransactionDetails/TransactionDetailsAllocation/types.ts @@ -0,0 +1,8 @@ +import HistoryItemDetailsProps from 'components/Earn/EarnHistory/EarnHistoryItemDetails/types'; +import { AllocationEventTypeParsed } from 'hooks/queries/useHistory'; + +type TransactionDetailsAllocationProps = Omit & { + eventData: AllocationEventTypeParsed & { amount: bigint }; +}; + +export default TransactionDetailsAllocationProps; diff --git a/client/src/components/Home/HomeGridTransactions/ModalTransactionDetails/TransactionDetails/TransactionDetailsDateAndTime/TransactionDetailsDateAndTime.module.scss b/client/src/components/Home/HomeGridTransactions/ModalTransactionDetails/TransactionDetails/TransactionDetailsDateAndTime/TransactionDetailsDateAndTime.module.scss new file mode 100644 index 0000000000..befe157137 --- /dev/null +++ b/client/src/components/Home/HomeGridTransactions/ModalTransactionDetails/TransactionDetails/TransactionDetailsDateAndTime/TransactionDetailsDateAndTime.module.scss @@ -0,0 +1,3 @@ +.root { + color: $color-black; +} diff --git a/client/src/components/Home/HomeGridTransactions/ModalTransactionDetails/TransactionDetails/TransactionDetailsDateAndTime/TransactionDetailsDateAndTime.tsx b/client/src/components/Home/HomeGridTransactions/ModalTransactionDetails/TransactionDetails/TransactionDetailsDateAndTime/TransactionDetailsDateAndTime.tsx new file mode 100644 index 0000000000..5b901b08ff --- /dev/null +++ b/client/src/components/Home/HomeGridTransactions/ModalTransactionDetails/TransactionDetails/TransactionDetailsDateAndTime/TransactionDetailsDateAndTime.tsx @@ -0,0 +1,11 @@ +import React, { FC } from 'react'; + +import styles from './TransactionDetailsDateAndTime.module.scss'; +import TransactionDetailsDateAndTimeProps from './types'; +import { getHistoryItemDateAndTime } from './utils'; + +const TransactionDetailsDateAndTime: FC = ({ timestamp }) => ( +
{getHistoryItemDateAndTime(timestamp)}
+); + +export default TransactionDetailsDateAndTime; diff --git a/client/src/components/Home/HomeGridTransactions/ModalTransactionDetails/TransactionDetails/TransactionDetailsDateAndTime/index.tsx b/client/src/components/Home/HomeGridTransactions/ModalTransactionDetails/TransactionDetails/TransactionDetailsDateAndTime/index.tsx new file mode 100644 index 0000000000..9a6295f8d7 --- /dev/null +++ b/client/src/components/Home/HomeGridTransactions/ModalTransactionDetails/TransactionDetails/TransactionDetailsDateAndTime/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './TransactionDetailsDateAndTime'; diff --git a/client/src/components/Home/HomeGridTransactions/ModalTransactionDetails/TransactionDetails/TransactionDetailsDateAndTime/types.ts b/client/src/components/Home/HomeGridTransactions/ModalTransactionDetails/TransactionDetails/TransactionDetailsDateAndTime/types.ts new file mode 100644 index 0000000000..4b3e75c970 --- /dev/null +++ b/client/src/components/Home/HomeGridTransactions/ModalTransactionDetails/TransactionDetails/TransactionDetailsDateAndTime/types.ts @@ -0,0 +1,3 @@ +export default interface EarnHistoryItemDateAndTimeProps { + timestamp: string; +} diff --git a/client/src/components/Home/HomeGridTransactions/ModalTransactionDetails/TransactionDetails/TransactionDetailsDateAndTime/utils.ts b/client/src/components/Home/HomeGridTransactions/ModalTransactionDetails/TransactionDetails/TransactionDetailsDateAndTime/utils.ts new file mode 100644 index 0000000000..a44359a44a --- /dev/null +++ b/client/src/components/Home/HomeGridTransactions/ModalTransactionDetails/TransactionDetails/TransactionDetailsDateAndTime/utils.ts @@ -0,0 +1,6 @@ +import { format } from 'date-fns'; + +export function getHistoryItemDateAndTime(timestamp: string): string { + // Timestamp from subgraph is in seconds, needs to be changed to milliseconds. + return format(parseInt(timestamp, 10) * 1000, 'h:mmaaa, dd MMM yyyy'); +} diff --git a/client/src/components/Home/HomeGridTransactions/ModalTransactionDetails/TransactionDetails/TransactionDetailsRest/TransactionDetailsRest.module.scss b/client/src/components/Home/HomeGridTransactions/ModalTransactionDetails/TransactionDetails/TransactionDetailsRest/TransactionDetailsRest.module.scss new file mode 100644 index 0000000000..2b33e161b4 --- /dev/null +++ b/client/src/components/Home/HomeGridTransactions/ModalTransactionDetails/TransactionDetails/TransactionDetailsRest/TransactionDetailsRest.module.scss @@ -0,0 +1,8 @@ +.viewOnEtherscan { + padding: 0; + + &.isMultisig { + opacity: 0.5; + pointer-events: none; + } +} diff --git a/client/src/components/Home/HomeGridTransactions/ModalTransactionDetails/TransactionDetails/TransactionDetailsRest/TransactionDetailsRest.tsx b/client/src/components/Home/HomeGridTransactions/ModalTransactionDetails/TransactionDetails/TransactionDetailsRest/TransactionDetailsRest.tsx new file mode 100644 index 0000000000..df485a9220 --- /dev/null +++ b/client/src/components/Home/HomeGridTransactions/ModalTransactionDetails/TransactionDetails/TransactionDetailsRest/TransactionDetailsRest.tsx @@ -0,0 +1,90 @@ +import cx from 'classnames'; +import React, { FC } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useTransaction } from 'wagmi'; + +import TransactionDetailsDateAndTime from 'components/Home/HomeGridTransactions/ModalTransactionDetails/TransactionDetails/TransactionDetailsDateAndTime'; +import TransactionLabel from 'components/Home/HomeGridTransactions/TransactionLabel'; +import BoxRounded from 'components/ui/BoxRounded'; +import Sections from 'components/ui/BoxRounded/Sections/Sections'; +import { SectionProps } from 'components/ui/BoxRounded/Sections/types'; +import Button from 'components/ui/Button'; +import networkConfig from 'constants/networkConfig'; + +import styles from './TransactionDetailsRest.module.scss'; +import TransactionDetailsRestProps from './types'; + +const TransactionDetailsRest: FC = ({ + eventData, + type, + timestamp, + isFinalized = true, + isWaitingForTransactionInitialized, + isMultisig = false, +}) => { + const { t } = useTranslation('translation', { + keyPrefix: 'components.home.homeGridTransactions.modalTransactionDetails', + }); + const { data: transaction, isFetching: isFetchingTransaction } = useTransaction({ + hash: isWaitingForTransactionInitialized ? undefined : eventData.transactionHash, + }); + + const isPatronDonation = type === 'patron_mode_donation'; + + const sections: SectionProps[] = [ + { + dataTest: isPatronDonation + ? 'EarnHistoryItemDetailsRest__matchingFundDonation' + : 'EarnHistoryItemDetailsRest__amount', + doubleValueProps: { + dataTest: isPatronDonation + ? 'EarnHistoryItemDetailsRest__matchingFundDonation__DoubleValue' + : 'EarnHistoryItemDetailsRest__amount__DoubleValue', + valueCrypto: eventData.amount, + ...(['withdrawal', 'patron_mode_donation'].includes(type) + ? { cryptoCurrency: 'ethereum', getFormattedEthValueProps: { shouldIgnoreGwei: true } } + : { cryptoCurrency: 'golem' }), + showCryptoSuffix: true, + }, + label: isPatronDonation ? t('sections.matchingFundDonation') : t('sections.amount'), + }, + ...((!isPatronDonation + ? [ + { + doubleValueProps: { + cryptoCurrency: 'ethereum', + getFormattedEthValueProps: { shouldIgnoreGwei: true }, + // Gas price is not known for pending transactions. + isFetching: isFetchingTransaction || isWaitingForTransactionInitialized, + showCryptoSuffix: true, + valueCrypto: BigInt(transaction?.gasPrice ?? 0), + }, + label: t('sections.gasPrice'), + }, + { + childrenLeft: , + childrenRight: ( + + ) : null + } + > +
+ {areAllocationsEmpty ? ( +
+ +
+ +
+
+ ) : ( + + )} +
+ + ); +}; + +export default HomeGridDonations; diff --git a/client/src/components/Home/HomeGridDonations/index.tsx b/client/src/components/Home/HomeGridDonations/index.tsx new file mode 100644 index 0000000000..0a66ad2aec --- /dev/null +++ b/client/src/components/Home/HomeGridDonations/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './HomeGridDonations'; diff --git a/client/src/components/Home/HomeGridDonations/types.ts b/client/src/components/Home/HomeGridDonations/types.ts new file mode 100644 index 0000000000..bb192c9fba --- /dev/null +++ b/client/src/components/Home/HomeGridDonations/types.ts @@ -0,0 +1,3 @@ +export default interface HomeGridDonationsProps { + className?: string; +} diff --git a/client/src/components/shared/Grid/GridTile/GridTile.module.scss b/client/src/components/shared/Grid/GridTile/GridTile.module.scss index 73188273a3..28636532cc 100644 --- a/client/src/components/shared/Grid/GridTile/GridTile.module.scss +++ b/client/src/components/shared/Grid/GridTile/GridTile.module.scss @@ -9,12 +9,16 @@ max-height: 100%; } -.groupTitleWrapper { +.titleWrapper { padding: 2.4rem; display: flex; align-items: center; margin: 0; + &.showTitleDivider { + border-bottom: 0.1rem solid $color-octant-grey3; + } + .title { padding: 0 1rem; height: 3.2rem; @@ -29,4 +33,3 @@ border-radius: $border-radius-10; } } -// } diff --git a/client/src/components/shared/Grid/GridTile/GridTile.tsx b/client/src/components/shared/Grid/GridTile/GridTile.tsx index 7a5c450e0b..1b366c528d 100644 --- a/client/src/components/shared/Grid/GridTile/GridTile.tsx +++ b/client/src/components/shared/Grid/GridTile/GridTile.tsx @@ -10,10 +10,11 @@ const GridTile: FC = ({ children, className, dataTest = 'GridTile', + showTitleDivider, }) => (
-
-
+
+
{title}
{titleSuffix} diff --git a/client/src/components/shared/Grid/GridTile/types.ts b/client/src/components/shared/Grid/GridTile/types.ts index 4434f2c628..9daa6f5a1e 100644 --- a/client/src/components/shared/Grid/GridTile/types.ts +++ b/client/src/components/shared/Grid/GridTile/types.ts @@ -4,8 +4,9 @@ type GridTileProps = { children: ReactNode; className?: string; dataTest?: string; - title: string; + title: string | ReactNode; titleSuffix?: ReactNode; + showTitleDivider?: boolean; }; export default GridTileProps; diff --git a/client/src/locales/en/translation.json b/client/src/locales/en/translation.json index 491da57cf2..5bf4807fa7 100644 --- a/client/src/locales/en/translation.json +++ b/client/src/locales/en/translation.json @@ -187,6 +187,12 @@ "pending": "Pending", "pendingMultisig": "Pending multisig" } + }, + "homeGridDonations": { + "donations": "Donations", + "donationHistory": "Donation history", + "noDonationsYet": "No donations yet. Lock GLM
and earn rewards to donate", + "edit": "Edit" } }, "settings": { From e0a14361273632a644feba725f3e8e2c2b76a947 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Miko=C5=82ajczyk?= Date: Tue, 3 Sep 2024 17:19:22 +0200 Subject: [PATCH 061/241] oct-1873: home donations update --- .../src/components/Home/HomeGrid/HomeGrid.tsx | 6 ++--- .../DonationsList/DonationsList.tsx | 5 ++-- .../HomeGridDonations/DonationsList/types.ts | 2 +- .../HomeGridDonations/HomeGridDonations.tsx | 24 +++++++++---------- .../components/shared/Grid/GridTile/types.ts | 2 +- 5 files changed, 19 insertions(+), 20 deletions(-) diff --git a/client/src/components/Home/HomeGrid/HomeGrid.tsx b/client/src/components/Home/HomeGrid/HomeGrid.tsx index 567fa0f53c..745d10a48e 100644 --- a/client/src/components/Home/HomeGrid/HomeGrid.tsx +++ b/client/src/components/Home/HomeGrid/HomeGrid.tsx @@ -2,14 +2,14 @@ import React, { memo, ReactNode } from 'react'; import { useAccount } from 'wagmi'; import HomeGridCurrentGlmLock from 'components/Home/HomeGridCurrentGlmLock'; +import HomeGridDivider from 'components/Home/HomeGridDivider'; +import HomeGridDonations from 'components/Home/HomeGridDonations'; import HomeGridPersonalAllocation from 'components/Home/HomeGridPersonalAllocation'; import HomeGridRewardsEstimator from 'components/Home/HomeGridRewardsEstimator'; import HomeGridTransactions from 'components/Home/HomeGridTransactions'; import HomeGridUQScore from 'components/Home/HomeGridUQScore'; -import HomeGridDonations from 'components/Home/HomeGridDonations'; -import HomeGridDivider from 'components/Home/HomeGridDivider'; -import useMediaQuery from 'hooks/helpers/useMediaQuery'; import Grid from 'components/shared/Grid'; +import useMediaQuery from 'hooks/helpers/useMediaQuery'; import styles from './HomeGrid.module.scss'; diff --git a/client/src/components/Home/HomeGridDonations/DonationsList/DonationsList.tsx b/client/src/components/Home/HomeGridDonations/DonationsList/DonationsList.tsx index 762d0fc297..f4a7917890 100644 --- a/client/src/components/Home/HomeGridDonations/DonationsList/DonationsList.tsx +++ b/client/src/components/Home/HomeGridDonations/DonationsList/DonationsList.tsx @@ -1,11 +1,10 @@ import React, { FC } from 'react'; -import DonationsListSkeletonItem from 'components/Home/HomeGridDonations/DonationsListSkeletonItem'; import DonationsListItem from 'components/Home/HomeGridDonations/DonationsListItem'; +import DonationsListSkeletonItem from 'components/Home/HomeGridDonations/DonationsListSkeletonItem'; import useGetValuesToDisplay from 'hooks/helpers/useGetValuesToDisplay'; import styles from './DonationsList.module.scss'; - import DonationsListProps from './types'; const DonationsList: FC = ({ @@ -37,8 +36,8 @@ const DonationsList: FC = ({ shouldIgnoreGwei: true, shouldIgnoreWei: true, }, - valueCrypto: donation.value, showCryptoSuffix: true, + valueCrypto: donation.value, }).primary } /> diff --git a/client/src/components/Home/HomeGridDonations/DonationsList/types.ts b/client/src/components/Home/HomeGridDonations/DonationsList/types.ts index f83fbce8c2..1a0f5fc634 100644 --- a/client/src/components/Home/HomeGridDonations/DonationsList/types.ts +++ b/client/src/components/Home/HomeGridDonations/DonationsList/types.ts @@ -2,7 +2,7 @@ import { ResponseItem } from 'hooks/helpers/useUserAllocationsAllEpochs'; export default interface DonationsListProps { dataTest?: string; + donations: ResponseItem['elements']; isLoading: boolean; numberOfSkeletons: number; - donations: ResponseItem['elements']; } diff --git a/client/src/components/Home/HomeGridDonations/HomeGridDonations.tsx b/client/src/components/Home/HomeGridDonations/HomeGridDonations.tsx index 088e5712c1..1426e61f24 100644 --- a/client/src/components/Home/HomeGridDonations/HomeGridDonations.tsx +++ b/client/src/components/Home/HomeGridDonations/HomeGridDonations.tsx @@ -1,20 +1,19 @@ import React, { FC, useMemo } from 'react'; import { Trans, useTranslation } from 'react-i18next'; +import { useAccount } from 'wagmi'; -import GridTile from 'components/shared/Grid/GridTile'; - -import useUserAllocations from 'hooks/queries/useUserAllocations'; -import useIsDecisionWindowOpen from 'hooks/queries/useIsDecisionWindowOpen'; -import useUserAllocationsAllEpochs from 'hooks/helpers/useUserAllocationsAllEpochs'; import DonationsList from 'components/Home/HomeGridDonations/DonationsList'; - import { getReducedUserAllocationsAllEpochs } from 'components/Metrics/MetricsPersonal/MetricsPersonalGridAllocations/utils'; +import GridTile from 'components/shared/Grid/GridTile'; +import Button from 'components/ui/Button'; import Img from 'components/ui/Img'; +import useUserAllocationsAllEpochs from 'hooks/helpers/useUserAllocationsAllEpochs'; +import useIsDecisionWindowOpen from 'hooks/queries/useIsDecisionWindowOpen'; +import useUserAllocations from 'hooks/queries/useUserAllocations'; import styles from './HomeGridDonations.module.scss'; import HomeGridDonationsProps from './types'; -import Button from 'components/ui/Button'; -import { useAccount } from 'wagmi'; + const HomeGridDonations: FC = ({ className }) => { const { t } = useTranslation('translation', { keyPrefix: 'components.home.homeGridDonations', @@ -34,8 +33,8 @@ const HomeGridDonations: FC = ({ className }) => { return ( {!isDecisionWindowOpen && !areAllocationsEmpty ? t('donationHistory') : t('donations')} @@ -48,7 +47,8 @@ const HomeGridDonations: FC = ({ className }) => { } titleSuffix={ isDecisionWindowOpen && userAllocations?.hasUserAlreadyDoneAllocation ? ( - ) : null @@ -59,14 +59,14 @@ const HomeGridDonations: FC = ({ className }) => {
- +
) : ( )}
diff --git a/client/src/components/shared/Grid/GridTile/types.ts b/client/src/components/shared/Grid/GridTile/types.ts index 9daa6f5a1e..eaadb3f48b 100644 --- a/client/src/components/shared/Grid/GridTile/types.ts +++ b/client/src/components/shared/Grid/GridTile/types.ts @@ -4,9 +4,9 @@ type GridTileProps = { children: ReactNode; className?: string; dataTest?: string; + showTitleDivider?: boolean; title: string | ReactNode; titleSuffix?: ReactNode; - showTitleDivider?: boolean; }; export default GridTileProps; From c3354b674dc258b56c12ce784c4fe5f0d6ebfd92 Mon Sep 17 00:00:00 2001 From: Housekeeper Bot Date: Wed, 4 Sep 2024 08:57:41 +0000 Subject: [PATCH 062/241] [CI/CD] Update uat.env contracts --- ci/argocd/contracts/uat.env | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/ci/argocd/contracts/uat.env b/ci/argocd/contracts/uat.env index c5985f74a1..c616fb5153 100644 --- a/ci/argocd/contracts/uat.env +++ b/ci/argocd/contracts/uat.env @@ -1,8 +1,8 @@ -BLOCK_NUMBER=6587595 +BLOCK_NUMBER=6630385 GLM_CONTRACT_ADDRESS=0x71432DD1ae7DB41706ee6a22148446087BdD0906 -AUTH_CONTRACT_ADDRESS=0x21Ba8588F489Bd6c61637E2EA8B183e82c715f5b -DEPOSITS_CONTRACT_ADDRESS=0xa911dabc7b75aFb419b274Dc212753F8Fe180CdE -EPOCHS_CONTRACT_ADDRESS=0xA46ad0Ed0D2B165c7689B597eDd73eA5ecdDE233 -PROPOSALS_CONTRACT_ADDRESS=0x98d6E28bcFA50e04Fb6296ca866F163fbEE09404 -WITHDRAWALS_TARGET_CONTRACT_ADDRESS=0xe3203052f1892d9a68EB8106128C67F9AFA34C09 -VAULT_CONTRACT_ADDRESS=0x0a3F605D52b39f23ADDddE83Bf8f8ef824FB870b +AUTH_CONTRACT_ADDRESS=0x609Cb06f4e31Cf6b8B1b873B30A356239992dcb6 +DEPOSITS_CONTRACT_ADDRESS=0xFaEBEF6d3018DCf5ad185306D52A2b4795c4a137 +EPOCHS_CONTRACT_ADDRESS=0xFfe9e46e4CeD64B47d95Fd2CE5d73B1fEcee95a5 +PROPOSALS_CONTRACT_ADDRESS=0x9b0edE47dEe6cd43D064eA4832ea58fA107eB6e8 +WITHDRAWALS_TARGET_CONTRACT_ADDRESS=0xbb31bFB190E3F14CCe391769389d0bE439038dd0 +VAULT_CONTRACT_ADDRESS=0xD0f9e662022D5e8EedDAD3514899bec5Fe168B40 From 5f1bdfd3b37ecf4ab5b9ae2d1321116f787249db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Zi=C3=B3=C5=82ek?= Date: Wed, 4 Sep 2024 12:44:42 +0200 Subject: [PATCH 063/241] feat: Farcaster & Twitter links in footer --- .../LayoutFooter/LayoutFooter.module.scss | 6 ++++++ .../Layout/LayoutFooter/LayoutFooter.tsx | 18 ++++++++++++++---- client/src/constants/urls.ts | 3 ++- client/src/locales/en/translation.json | 6 ++++-- 4 files changed, 26 insertions(+), 7 deletions(-) diff --git a/client/src/components/shared/Layout/LayoutFooter/LayoutFooter.module.scss b/client/src/components/shared/Layout/LayoutFooter/LayoutFooter.module.scss index 45286af756..cdccb18f15 100644 --- a/client/src/components/shared/Layout/LayoutFooter/LayoutFooter.module.scss +++ b/client/src/components/shared/Layout/LayoutFooter/LayoutFooter.module.scss @@ -67,6 +67,12 @@ font-size: $font-size-14; font-weight: $font-weight-semibold; color: $color-octant-grey5; + + @media #{$tablet-down} { + &:not(.isVisibleAtLowerWidth) { + display: none; + } + } } } diff --git a/client/src/components/shared/Layout/LayoutFooter/LayoutFooter.tsx b/client/src/components/shared/Layout/LayoutFooter/LayoutFooter.tsx index e7f1a0db2c..b2cdc400b2 100644 --- a/client/src/components/shared/Layout/LayoutFooter/LayoutFooter.tsx +++ b/client/src/components/shared/Layout/LayoutFooter/LayoutFooter.tsx @@ -7,10 +7,12 @@ import { BLOG_POST, BRAND_ASSETS_FIGMA_LINK, DISCORD_LINK, + FARCASTER_LINK, GOLEM_FOUNDATION_LINK, OCTANT_BUILD_LINK, OCTANT_DOCS, TERMS_OF_USE, + TWITTER_LINK, } from 'constants/urls'; import { octantSemiTransparent } from 'svg/logo'; @@ -23,9 +25,11 @@ const LayoutFooter: FC = ({ className }) => { const links = [ { label: t('links.website'), link: OCTANT_BUILD_LINK }, { label: t('links.discord'), link: DISCORD_LINK }, - { label: t('links.blog'), link: BLOG_POST }, + { isVisibleAtLowerWidth: false, label: t('links.blog'), link: BLOG_POST }, { label: t('links.docs'), link: OCTANT_DOCS }, - { label: t('links.brandAssets'), link: BRAND_ASSETS_FIGMA_LINK }, + { label: t('links.farcaster'), link: FARCASTER_LINK }, + { label: t('links.twitterX'), link: TWITTER_LINK }, + { isVisibleAtLowerWidth: false, label: t('links.brandAssets'), link: BRAND_ASSETS_FIGMA_LINK }, { label: t('links.termsOfUse'), link: TERMS_OF_USE }, ]; @@ -49,8 +53,14 @@ const LayoutFooter: FC = ({ className }) => {
- {links.map(({ link, label }) => ( - + {links.map(({ link, label, isVisibleAtLowerWidth = true }) => ( + {`→ ${label}`} ))} diff --git a/client/src/constants/urls.ts b/client/src/constants/urls.ts index 7e71ee1e0a..22362f3872 100644 --- a/client/src/constants/urls.ts +++ b/client/src/constants/urls.ts @@ -5,7 +5,8 @@ export const OCTANT_DOCS = 'https://docs.octant.app/'; export const DISCORD_LINK = 'https://discord.gg/octant'; export const BLOG_POST = 'https://blog.octant.build/'; export const OCTANT_BUILD_LINK = 'https://octant.build/'; -export const TWITTER_LINK = 'https://twitter.com/OctantApp'; +export const TWITTER_LINK = 'https://x.com/OctantApp'; +export const FARCASTER_LINK = 'https://warpcast.com/octant'; export const TERMS_OF_USE = 'https://docs.octant.app/terms-of-use.html'; export const GITCOIN_PASSPORT = 'https://support.passport.xyz/passport-knowledge-base'; export const GITCOIN_PASSPORT_CUSTOM_OCTANT_DASHBOARD = 'https://passport.gitcoin.co/#/octant/'; diff --git a/client/src/locales/en/translation.json b/client/src/locales/en/translation.json index f34099f890..9285015760 100644 --- a/client/src/locales/en/translation.json +++ b/client/src/locales/en/translation.json @@ -364,7 +364,9 @@ "blog": "Blog", "docs": "Docs", "brandAssets": "Brand assets", - "termsOfUse": "Terms of use" + "termsOfUse": "Terms of use", + "farcaster": "Farcaster", + "twitterX": "Twitter/X" } }, "main": { @@ -649,4 +651,4 @@ "information": "We're synchronizing things to prepare the
next epoch, so the app will be unavailable
for a little while. Please check back soon." } } -} \ No newline at end of file +} From 9674fab13149021412e252b397f9c6ba8b5461fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Zi=C3=B3=C5=82ek?= Date: Wed, 4 Sep 2024 14:06:24 +0200 Subject: [PATCH 064/241] feat: projects grid --- .../src/components/Home/HomeGrid/HomeGrid.tsx | 4 +- .../ProjectsList/ProjectsList.module.scss | 7 --- .../Projects/ProjectsList/ProjectsList.tsx | 55 ++++++++++--------- 3 files changed, 31 insertions(+), 35 deletions(-) diff --git a/client/src/components/Home/HomeGrid/HomeGrid.tsx b/client/src/components/Home/HomeGrid/HomeGrid.tsx index 49524d3e23..e084e95388 100644 --- a/client/src/components/Home/HomeGrid/HomeGrid.tsx +++ b/client/src/components/Home/HomeGrid/HomeGrid.tsx @@ -1,4 +1,4 @@ -import React, { memo, ReactNode } from 'react'; +import React, { memo, ReactElement } from 'react'; import HomeGridCurrentGlmLock from 'components/Home/HomeGridCurrentGlmLock'; import HomeGridPersonalAllocation from 'components/Home/HomeGridPersonalAllocation'; @@ -6,7 +6,7 @@ import Grid from 'components/shared/Grid'; import styles from './HomeGrid.module.scss'; -const HomeGrid = (): ReactNode => ( +const HomeGrid = (): ReactElement => ( diff --git a/client/src/components/Projects/ProjectsList/ProjectsList.module.scss b/client/src/components/Projects/ProjectsList/ProjectsList.module.scss index 62009b0300..ba476e4a0b 100644 --- a/client/src/components/Projects/ProjectsList/ProjectsList.module.scss +++ b/client/src/components/Projects/ProjectsList/ProjectsList.module.scss @@ -14,13 +14,6 @@ $elementMargin: 1.6rem; } } -.list { - display: flex; - flex-wrap: wrap; - width: 100%; - justify-content: space-between; -} - .inputSearch { margin-bottom: $elementMargin; } diff --git a/client/src/components/Projects/ProjectsList/ProjectsList.tsx b/client/src/components/Projects/ProjectsList/ProjectsList.tsx index 7174b465a0..ae452c24b3 100644 --- a/client/src/components/Projects/ProjectsList/ProjectsList.tsx +++ b/client/src/components/Projects/ProjectsList/ProjectsList.tsx @@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next'; import ProjectsListItem from 'components/Projects/ProjectsListItem'; import ProjectsListSkeletonItem from 'components/Projects/ProjectsListSkeletonItem'; +import Grid from 'components/shared/Grid'; import Img from 'components/ui/Img'; import InputText from 'components/ui/InputText/InputText'; import Svg from 'components/ui/Svg'; @@ -115,34 +116,36 @@ const ProjectsList: FC = ({ {t('noSearchResults')}
)} - {areProjectsIpfsWithRewardsAvailable - ? projectsAddressesToIterate.map((address, index) => { - const projectIpfsWithRewards = projectsIpfsWithRewardsFiltered.find( - element => element.address === address, - ); + + {areProjectsIpfsWithRewardsAvailable + ? projectsAddressesToIterate.map((address, index) => { + const projectIpfsWithRewards = projectsIpfsWithRewardsFiltered.find( + element => element.address === address, + ); - if (!projectIpfsWithRewards) { - return null; - } + if (!projectIpfsWithRewards) { + return null; + } - return ( - - ); - }) - : projectsEpoch?.projectsAddresses?.map((_, index) => ( - // eslint-disable-next-line react/no-array-index-key - - ))} + return ( + + ); + }) + : projectsEpoch?.projectsAddresses?.map((_, index) => ( + // eslint-disable-next-line react/no-array-index-key + + ))} +
); }; From 898c571952f2bb1a171d7f6dee4d618d579e5cce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Miko=C5=82ajczyk?= Date: Wed, 4 Sep 2024 15:42:40 +0200 Subject: [PATCH 065/241] oct-1890: settings drawer --- client/src/App.tsx | 5 +- .../HomeGridDonations/HomeGridDonations.tsx | 2 +- .../HomeGridRewardsEstimator.tsx | 1 + .../components/Settings/Settings.module.scss | 57 ++++++ client/src/components/Settings/Settings.tsx | 42 +++++ .../SettingsCurrencyBox.tsx | 2 +- .../Settings/SettingsCurrencyBox}/types.ts | 0 .../SettingsLinkBoxes.module.scss | 44 ----- .../SettingsLinkBoxes/SettingsLinkBoxes.tsx | 57 ------ .../SettingsMainInfoBox.module.scss | 13 +- .../SettingsMainInfoBox.tsx | 10 -- .../{SettingsLinkBoxes => }/index.tsx | 2 +- .../src/components/shared/Layout/Layout.tsx | 11 +- .../shared/Layout/LayoutFooter/index.tsx | 2 + .../LayoutTopBar/LayoutTopBar.module.scss | 42 ++++- .../Layout/LayoutTopBar/LayoutTopBar.tsx | 32 +++- .../shared/Layout/LayoutTopBar/index.tsx | 2 + client/src/components/shared/Layout/types.ts | 1 + .../components/ui/Drawer/Drawer.module.scss | 18 ++ client/src/components/ui/Drawer/Drawer.tsx | 38 ++++ client/src/components/ui/Drawer/index.tsx | 2 + client/src/components/ui/Drawer/types.ts | 7 + client/src/routes/RootRoutes/RootRoutes.tsx | 89 +++------- client/src/routes/RootRoutes/types.ts | 10 -- client/src/styles/utils/_colors.scss | 1 + client/src/views/HomeView/HomeView.tsx | 5 +- client/src/views/MetricsView/MetricsView.tsx | 14 +- client/src/views/ProjectView/ProjectView.tsx | 4 +- .../src/views/ProjectsView/ProjectsView.tsx | 5 +- .../SettingsView/SettingsView.module.scss | 168 ------------------ .../src/views/SettingsView/SettingsView.tsx | 39 +--- 31 files changed, 291 insertions(+), 434 deletions(-) create mode 100644 client/src/components/Settings/Settings.module.scss create mode 100644 client/src/components/Settings/Settings.tsx rename client/src/{views/SettingsView => components/Settings/SettingsCurrencyBox}/types.ts (100%) delete mode 100644 client/src/components/Settings/SettingsLinkBoxes/SettingsLinkBoxes.module.scss delete mode 100644 client/src/components/Settings/SettingsLinkBoxes/SettingsLinkBoxes.tsx rename client/src/components/Settings/{SettingsLinkBoxes => }/index.tsx (51%) create mode 100644 client/src/components/shared/Layout/LayoutFooter/index.tsx create mode 100644 client/src/components/shared/Layout/LayoutTopBar/index.tsx create mode 100644 client/src/components/ui/Drawer/Drawer.module.scss create mode 100644 client/src/components/ui/Drawer/Drawer.tsx create mode 100644 client/src/components/ui/Drawer/index.tsx create mode 100644 client/src/components/ui/Drawer/types.ts delete mode 100644 client/src/routes/RootRoutes/types.ts delete mode 100644 client/src/views/SettingsView/SettingsView.module.scss diff --git a/client/src/App.tsx b/client/src/App.tsx index 0d88e6d9c1..e78b2da3ea 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -3,6 +3,7 @@ import React, { ReactElement, useState, Fragment } from 'react'; import { useAccount } from 'wagmi'; import AppLoader from 'components/shared/AppLoader'; +import Layout from 'components/shared/Layout'; import ModalOnboarding from 'components/shared/ModalOnboarding/ModalOnboarding'; import OnboardingStepper from 'components/shared/OnboardingStepper'; import useAppConnectManager from 'hooks/helpers/useAppConnectManager'; @@ -44,7 +45,9 @@ const App = (): ReactElement => { return ( - + + + {!isSyncingInProgress && !isProjectAdminMode && } {isConnected && !isOnboardingDone && !isOnboardingModalOpen && } diff --git a/client/src/components/Home/HomeGridDonations/HomeGridDonations.tsx b/client/src/components/Home/HomeGridDonations/HomeGridDonations.tsx index 1426e61f24..f498d2fcbc 100644 --- a/client/src/components/Home/HomeGridDonations/HomeGridDonations.tsx +++ b/client/src/components/Home/HomeGridDonations/HomeGridDonations.tsx @@ -1,4 +1,4 @@ -import React, { FC, useMemo } from 'react'; +import React, { FC } from 'react'; import { Trans, useTranslation } from 'react-i18next'; import { useAccount } from 'wagmi'; diff --git a/client/src/components/Home/HomeGridRewardsEstimator/HomeGridRewardsEstimator.tsx b/client/src/components/Home/HomeGridRewardsEstimator/HomeGridRewardsEstimator.tsx index 180fce86f4..9b615c41e5 100644 --- a/client/src/components/Home/HomeGridRewardsEstimator/HomeGridRewardsEstimator.tsx +++ b/client/src/components/Home/HomeGridRewardsEstimator/HomeGridRewardsEstimator.tsx @@ -132,6 +132,7 @@ const HomeGridRewardsEstimator: FC = ({ className isButtonClearVisible={false} label={t('enterGLMAmount')} onChange={e => onCryptoValueChange(e.target.value)} + shouldAutoFocusAndSelect={false} suffix="GLM" value={formik.values.valueCrypto} /> diff --git a/client/src/components/Settings/Settings.module.scss b/client/src/components/Settings/Settings.module.scss new file mode 100644 index 0000000000..629e806183 --- /dev/null +++ b/client/src/components/Settings/Settings.module.scss @@ -0,0 +1,57 @@ +.root { + padding-bottom: 8.8rem; + width: 100%; + display: flex; + flex-direction: column; + + @media #{$tablet-up} { + padding-bottom: 14.4rem; + } + + @media #{$desktop-up} { + padding-bottom: 0; + width: 42.4rem; + min-height: 0; + } + + .title { + padding: 0; + display: flex; + align-items: center; + min-height: 8.8rem; + width: 100%; + color: $color-octant-dark; + font-size: $font-size-24; + font-weight: $font-weight-bold; + line-height: 1.4rem; + + @media #{$tablet-up} { + font-size: $font-size-32; + min-height: 14.4rem; + } + + @media #{$desktop-up} { + padding: 0 4rem; + } + } + + .mainInfoBoxWrapper { + flex-direction: column; + display: flex; + gap: 1.6rem; + width: 100%; + + @media #{$desktop-up} { + flex-direction: row; + } + } + + .boxesWrapper { + width: 100%; + + @media #{$desktop-up} { + padding: 0 4rem 4rem; + overflow: auto; + } + } +} diff --git a/client/src/components/Settings/Settings.tsx b/client/src/components/Settings/Settings.tsx new file mode 100644 index 0000000000..de4f5914e1 --- /dev/null +++ b/client/src/components/Settings/Settings.tsx @@ -0,0 +1,42 @@ +import React, { ReactElement } from 'react'; +import { useAccount } from 'wagmi'; + +import SettingsCryptoMainValueBox from 'components/Settings/SettingsCryptoMainValueBox'; +import SettingsCurrencyBox from 'components/Settings/SettingsCurrencyBox'; +import SettingsMainInfoBox from 'components/Settings/SettingsMainInfoBox'; +import SettingsPatronModeBox from 'components/Settings/SettingsPatronModeBox'; +import SettingsShowOnboardingBox from 'components/Settings/SettingsShowOnboardingBox'; +import SettingsShowTipsBox from 'components/Settings/SettingsShowTipsBox'; +import useIsProjectAdminMode from 'hooks/helpers/useIsProjectAdminMode'; + +import styles from './Settings.module.scss'; + +const Settings = (): ReactElement => { + const { isConnected } = useAccount(); + + const isProjectAdminMode = useIsProjectAdminMode(); + + return ( +
+
Settings
+
+ {!isProjectAdminMode && ( +
+ +
+ )} + + + {isConnected && !isProjectAdminMode && } + {!isProjectAdminMode && ( + <> + + + + )} +
+
+ ); +}; + +export default Settings; diff --git a/client/src/components/Settings/SettingsCurrencyBox/SettingsCurrencyBox.tsx b/client/src/components/Settings/SettingsCurrencyBox/SettingsCurrencyBox.tsx index 777112f9f4..9b93740481 100644 --- a/client/src/components/Settings/SettingsCurrencyBox/SettingsCurrencyBox.tsx +++ b/client/src/components/Settings/SettingsCurrencyBox/SettingsCurrencyBox.tsx @@ -5,9 +5,9 @@ import BoxRounded from 'components/ui/BoxRounded'; import InputSelect from 'components/ui/InputSelect'; import useSettingsStore from 'store/settings/store'; import { SettingsData } from 'store/settings/types'; -import { Options } from 'views/SettingsView/types'; import styles from './SettingsCurrencyBox.module.scss'; +import { Options } from './types'; const options: Options = [ { label: 'USD', value: 'usd' }, diff --git a/client/src/views/SettingsView/types.ts b/client/src/components/Settings/SettingsCurrencyBox/types.ts similarity index 100% rename from client/src/views/SettingsView/types.ts rename to client/src/components/Settings/SettingsCurrencyBox/types.ts diff --git a/client/src/components/Settings/SettingsLinkBoxes/SettingsLinkBoxes.module.scss b/client/src/components/Settings/SettingsLinkBoxes/SettingsLinkBoxes.module.scss deleted file mode 100644 index 7b2a53f78d..0000000000 --- a/client/src/components/Settings/SettingsLinkBoxes/SettingsLinkBoxes.module.scss +++ /dev/null @@ -1,44 +0,0 @@ -.root { - width: 100%; - display: flex; - gap: 1.5rem; - margin-top: 1.6rem; - - .boxChildrenWrapper { - height: 100%; - } - - .box { - font-size: $font-size-12; - line-height: 2rem; - font-weight: $font-weight-bold; - display: flex; - align-items: center; - justify-content: center; - height: 6.4rem; - } - - .buttonLink { - display: block; - font-size: $font-size-12; - line-height: 2rem; - font-weight: $font-weight-semibold; - min-height: 2rem; - width: 100%; - height: 100%; - display: flex; - align-items: center; - - .buttonLinkText { - margin-left: 0.4rem; - } - - .buttonLinkArrowSvg { - margin-left: 0.4rem; - } - - @media #{$desktop-up} { - font-size: $font-size-14; - } - } -} diff --git a/client/src/components/Settings/SettingsLinkBoxes/SettingsLinkBoxes.tsx b/client/src/components/Settings/SettingsLinkBoxes/SettingsLinkBoxes.tsx deleted file mode 100644 index 6e1e3b63c5..0000000000 --- a/client/src/components/Settings/SettingsLinkBoxes/SettingsLinkBoxes.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import React, { ReactNode, memo } from 'react'; -import { useTranslation } from 'react-i18next'; - -import BoxRounded from 'components/ui/BoxRounded'; -import Button from 'components/ui/Button'; -import Svg from 'components/ui/Svg'; -import { DISCORD_LINK, OCTANT_BUILD_LINK, OCTANT_DOCS } from 'constants/urls'; -import useMediaQuery from 'hooks/helpers/useMediaQuery'; -import { arrowTopRight } from 'svg/misc'; - -import styles from './SettingsLinkBoxes.module.scss'; - -const SettingsLinkBoxes = (): ReactNode => { - const { t } = useTranslation('translation', { keyPrefix: 'views.settings' }); - const { isDesktop } = useMediaQuery(); - - const mobileLinks = [ - { - href: OCTANT_BUILD_LINK, - label: isDesktop ? t('visitWebsite') : t('website'), // 'Website', - }, - { - href: OCTANT_DOCS, - label: isDesktop ? t('checkOutDocs') : t('docs'), // 'Docs', - }, - { - href: DISCORD_LINK, - label: isDesktop ? t('joinOurDiscord') : t('discord'), - }, - ]; - - return ( -
- {mobileLinks.map(({ href, label }) => ( - - - - ))} -
- ); -}; - -export default memo(SettingsLinkBoxes); diff --git a/client/src/components/Settings/SettingsMainInfoBox/SettingsMainInfoBox.module.scss b/client/src/components/Settings/SettingsMainInfoBox/SettingsMainInfoBox.module.scss index a04dc60b9d..dc9f80e9c8 100644 --- a/client/src/components/Settings/SettingsMainInfoBox/SettingsMainInfoBox.module.scss +++ b/client/src/components/Settings/SettingsMainInfoBox/SettingsMainInfoBox.module.scss @@ -1,8 +1,8 @@ .root { padding: 4.4rem 2.2rem 0.5rem; - @media #{$desktop-up} { - padding: 4.6rem 2.2rem 4.3rem; + @media #{$tablet-up} { + padding: 4.6rem 2.2rem 1rem; } .infoTitle { @@ -17,7 +17,7 @@ font-weight: $font-weight-bold; line-height: 1.6rem; - @media #{$desktop-up} { + @media #{$tablet-up} { font-size: $font-size-14; } } @@ -33,10 +33,9 @@ color: $color-octant-grey5; } - @media #{$desktop-up} { - justify-content: flex-end; - min-height: 8rem; + @media #{$tablet-up} { margin-top: 3.6rem; + min-height: 8rem; .info { line-height: 2rem; @@ -56,7 +55,7 @@ font-weight: $font-weight-semibold; min-height: 2rem; - @media #{$desktop-up} { + @media #{$tablet-up} { font-size: $font-size-14; } } diff --git a/client/src/components/Settings/SettingsMainInfoBox/SettingsMainInfoBox.tsx b/client/src/components/Settings/SettingsMainInfoBox/SettingsMainInfoBox.tsx index b1df74421b..a1f6906157 100644 --- a/client/src/components/Settings/SettingsMainInfoBox/SettingsMainInfoBox.tsx +++ b/client/src/components/Settings/SettingsMainInfoBox/SettingsMainInfoBox.tsx @@ -3,9 +3,7 @@ import React, { ReactNode, memo } from 'react'; import { useTranslation } from 'react-i18next'; import BoxRounded from 'components/ui/BoxRounded'; -import Button from 'components/ui/Button'; import Svg from 'components/ui/Svg'; -import { TERMS_OF_USE } from 'constants/urls'; import useMediaQuery from 'hooks/helpers/useMediaQuery'; import useCurrentEpoch from 'hooks/queries/useCurrentEpoch'; import { octantWordmark } from 'svg/logo'; @@ -36,14 +34,6 @@ const SettingsMainInfoBox = (): ReactNode => { {t('golemFoundationProject')}
{t('poweredByCoinGeckoApi')}
-
); diff --git a/client/src/components/Settings/SettingsLinkBoxes/index.tsx b/client/src/components/Settings/index.tsx similarity index 51% rename from client/src/components/Settings/SettingsLinkBoxes/index.tsx rename to client/src/components/Settings/index.tsx index 957a823fa9..6b65b09ebe 100644 --- a/client/src/components/Settings/SettingsLinkBoxes/index.tsx +++ b/client/src/components/Settings/index.tsx @@ -1,2 +1,2 @@ // eslint-disable-next-line no-restricted-exports -export { default } from './SettingsLinkBoxes'; +export { default } from './Settings'; diff --git a/client/src/components/shared/Layout/Layout.tsx b/client/src/components/shared/Layout/Layout.tsx index eee08a6cea..77b841adf7 100644 --- a/client/src/components/shared/Layout/Layout.tsx +++ b/client/src/components/shared/Layout/Layout.tsx @@ -13,7 +13,9 @@ import React, { // } from 'react-router-dom'; // import { useAccount } from 'wagmi'; +import LayoutFooter from 'components/shared/Layout/LayoutFooter'; import LayoutNavbar from 'components/shared/Layout/LayoutNavbar'; +import LayoutTopBar from 'components/shared/Layout/LayoutTopBar'; import ModalLayoutConnectWallet from 'components/shared/Layout/ModalLayoutConnectWallet'; import ModalLayoutWallet from 'components/shared/Layout/ModalLayoutWallet'; import Loader from 'components/ui/Loader'; @@ -29,6 +31,8 @@ import useMediaQuery from 'hooks/helpers/useMediaQuery'; // import useUserTOS from 'hooks/queries/useUserTOS'; // import { ROOT_ROUTES } from 'routes/RootRoutes/routes'; import useLayoutStore from 'store/layout/store'; +import SyncView from 'views/SyncView/SyncView'; + // import useSettingsStore from 'store/settings/store'; // import { chevronLeft } from 'svg/navigation'; // import getDifferenceInWeeks from 'utils/getDifferenceInWeeks'; @@ -37,8 +41,6 @@ import useLayoutStore from 'store/layout/store'; // import truncateEthAddress from 'utils/truncateEthAddress'; import styles from './Layout.module.scss'; -import LayoutFooter from './LayoutFooter/LayoutFooter'; -import LayoutTopBar from './LayoutTopBar/LayoutTopBar'; import LayoutProps from './types'; const Layout: FC = ({ @@ -49,6 +51,7 @@ const Layout: FC = ({ isLoading, isNavigationVisible = true, classNameBody, + isSyncingInProgress, // isAbsoluteHeaderPosition = false, // showHeaderBlur = true, }) => { @@ -153,6 +156,10 @@ const Layout: FC = ({ // // eslint-disable-next-line react-hooks/exhaustive-deps // }, [isDecisionWindowOpen, timeCurrentAllocationEnd, timeCurrentEpochEnd]); + if (isSyncingInProgress) { + return ; + } + return (
diff --git a/client/src/components/shared/Layout/LayoutFooter/index.tsx b/client/src/components/shared/Layout/LayoutFooter/index.tsx new file mode 100644 index 0000000000..a98eab3131 --- /dev/null +++ b/client/src/components/shared/Layout/LayoutFooter/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './LayoutFooter'; diff --git a/client/src/components/shared/Layout/LayoutTopBar/LayoutTopBar.module.scss b/client/src/components/shared/Layout/LayoutTopBar/LayoutTopBar.module.scss index b2dc3228e1..79d4c61f88 100644 --- a/client/src/components/shared/Layout/LayoutTopBar/LayoutTopBar.module.scss +++ b/client/src/components/shared/Layout/LayoutTopBar/LayoutTopBar.module.scss @@ -117,17 +117,45 @@ margin-left: 1.6rem; } - .allocateButtonIcon { - margin-left: 0.3rem; + .allocateButton { + &:hover { + .allocateButtonIcon path { + stroke: $color-octant-grey5; + } + } - path { - stroke: $color-octant-grey2; + &:active { + .allocateButtonIcon path { + stroke: $color-octant-dark; + } + } + + .allocateButtonIcon { + margin-left: 0.3rem; + + path { + stroke: $color-octant-grey2; + } } } - .settingsButtonIcon { - path { - stroke: $color-octant-grey2; + .settingsButton { + &:hover { + .settingsButtonIcon path { + stroke: $color-octant-grey5; + } + } + + &:active { + .settingsButtonIcon path { + stroke: $color-octant-dark; + } + } + + .settingsButtonIcon { + path { + stroke: $color-octant-grey2; + } } } } diff --git a/client/src/components/shared/Layout/LayoutTopBar/LayoutTopBar.tsx b/client/src/components/shared/Layout/LayoutTopBar/LayoutTopBar.tsx index 840044fdb9..73f2e41f2f 100644 --- a/client/src/components/shared/Layout/LayoutTopBar/LayoutTopBar.tsx +++ b/client/src/components/shared/Layout/LayoutTopBar/LayoutTopBar.tsx @@ -1,10 +1,12 @@ import cx from 'classnames'; -import React, { FC, Fragment, useMemo } from 'react'; +import React, { FC, Fragment, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useLocation, useNavigate } from 'react-router-dom'; import { useAccount } from 'wagmi'; +import Settings from 'components/Settings'; import Button from 'components/ui/Button'; +import Drawer from 'components/ui/Drawer'; import Svg from 'components/ui/Svg'; import useMediaQuery from 'hooks/helpers/useMediaQuery'; import useNavigationTabs from 'hooks/helpers/useNavigationTabs'; @@ -25,6 +27,7 @@ const LayoutTopBar: FC = ({ className }) => { const { isDesktop, isMobile } = useMediaQuery(); const { isConnected, address } = useAccount(); const { pathname } = useLocation(); + const [isSettingsDrawerOpen, setIsSettingsDrawerOpen] = useState(false); const navigate = useNavigate(); const { data: isDecisionWindowOpen } = useIsDecisionWindowOpen(); const { data: currentEpoch } = useCurrentEpoch(); @@ -68,6 +71,22 @@ const LayoutTopBar: FC = ({ className }) => { navigate(ROOT_ROUTES.home.absolute); }; + useEffect(() => { + if (pathname !== ROOT_ROUTES.settings.absolute || !isDesktop) { + return; + } + + setIsSettingsDrawerOpen(true); + }, [isDesktop, pathname]); + + useEffect(() => { + if (isSettingsDrawerOpen && pathname !== ROOT_ROUTES.settings.absolute && !isDesktop) { + navigate(ROOT_ROUTES.settings.absolute); + setIsSettingsDrawerOpen(false); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isDesktop, pathname, isSettingsDrawerOpen]); + return (
@@ -100,7 +119,11 @@ const LayoutTopBar: FC = ({ className }) => { {isDesktop && ( -
+
setIsSettingsDrawerOpen(prev => !prev)} + >
@@ -108,6 +131,11 @@ const LayoutTopBar: FC = ({ className }) => {
)} + {isDesktop && ( + setIsSettingsDrawerOpen(false)}> + + + )}
); }; diff --git a/client/src/components/shared/Layout/LayoutTopBar/index.tsx b/client/src/components/shared/Layout/LayoutTopBar/index.tsx new file mode 100644 index 0000000000..4c6077b31e --- /dev/null +++ b/client/src/components/shared/Layout/LayoutTopBar/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './LayoutTopBar'; diff --git a/client/src/components/shared/Layout/types.ts b/client/src/components/shared/Layout/types.ts index 4f253a41b6..5a374facab 100644 --- a/client/src/components/shared/Layout/types.ts +++ b/client/src/components/shared/Layout/types.ts @@ -8,6 +8,7 @@ export default interface LayoutProps { isHeaderVisible?: boolean; isLoading?: boolean; isNavigationVisible?: boolean; + isSyncingInProgress?: boolean; navigationBottomSuffix?: ReactNode; showHeaderBlur?: boolean; } diff --git a/client/src/components/ui/Drawer/Drawer.module.scss b/client/src/components/ui/Drawer/Drawer.module.scss new file mode 100644 index 0000000000..30ce9b42ab --- /dev/null +++ b/client/src/components/ui/Drawer/Drawer.module.scss @@ -0,0 +1,18 @@ +.root { + display: flex; + flex-direction: column; + position: fixed; + height: 100vh; + right: 0; + background: $color-octant-grey6; + top: 0; + z-index: $z-index-8; + box-shadow: 0 0 5rem 0 $color-black-08; + + .buttonClose { + position: absolute; + top: 4rem; + right: 4rem; + z-index: $z-index-6; + } +} diff --git a/client/src/components/ui/Drawer/Drawer.tsx b/client/src/components/ui/Drawer/Drawer.tsx new file mode 100644 index 0000000000..6f9f95a0cb --- /dev/null +++ b/client/src/components/ui/Drawer/Drawer.tsx @@ -0,0 +1,38 @@ +import { AnimatePresence, motion } from 'framer-motion'; +import React, { FC } from 'react'; +import { createPortal } from 'react-dom'; + +import Button from 'components/ui/Button'; +import Svg from 'components/ui/Svg'; +import { cross } from 'svg/misc'; + +import styles from './Drawer.module.scss'; +import DrawerProps from './types'; + +const Drawer: FC = ({ children, isOpen, onClose }) => + createPortal( + + {isOpen && ( + + + )} + + ) : null} +
+ ); +}; + +export default EpochResultsDetails; diff --git a/client/src/components/Home/HomeGridEpochResults/EpochResultsDetails/index.tsx b/client/src/components/Home/HomeGridEpochResults/EpochResultsDetails/index.tsx new file mode 100644 index 0000000000..77c87aedaa --- /dev/null +++ b/client/src/components/Home/HomeGridEpochResults/EpochResultsDetails/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './EpochResultsDetails'; diff --git a/client/src/components/Home/HomeGridEpochResults/EpochResultsDetails/types.ts b/client/src/components/Home/HomeGridEpochResults/EpochResultsDetails/types.ts new file mode 100644 index 0000000000..3cb78de65e --- /dev/null +++ b/client/src/components/Home/HomeGridEpochResults/EpochResultsDetails/types.ts @@ -0,0 +1,3 @@ +export default interface EpochResultsDetailsProps { + details?: any; +} diff --git a/client/src/components/Home/HomeGridEpochResults/HomeGridEpochResults.module.scss b/client/src/components/Home/HomeGridEpochResults/HomeGridEpochResults.module.scss new file mode 100644 index 0000000000..944d453ee3 --- /dev/null +++ b/client/src/components/Home/HomeGridEpochResults/HomeGridEpochResults.module.scss @@ -0,0 +1,60 @@ +.gridTile { + @media #{$tablet-up} { + grid-column: span 2; + } + + @media #{$desktop-up} { + grid-column: span 3; + } + + @media #{$large-desktop-up} { + grid-column: span 2; + } +} + +.root { + padding: 0 0.8rem 0.8rem; + display: flex; + flex-direction: column; + flex: 1; +} + +.arrowsWrapper { + user-select: none; + display: flex; + margin-left: auto; + + .arrow { + cursor: pointer; + width: 3.2rem; + height: 3.2rem; + background: $color-octant-grey8; + border-radius: $border-radius-10; + transition: all $transition-time-5; + display: flex; + align-items: center; + justify-content: center; + + &.leftArrow { + svg { + transform: rotate(180deg); + } + } + + &.isDisabled { + background: $color-octant-grey6; + + svg path { + fill: $color-octant-grey5; + } + } + + &:not(.isDisabled):hover { + background: $color-octant-grey1; + } + + &:first-child { + margin-right: 1.6rem; + } + } +} diff --git a/client/src/components/Home/HomeGridEpochResults/HomeGridEpochResults.tsx b/client/src/components/Home/HomeGridEpochResults/HomeGridEpochResults.tsx new file mode 100644 index 0000000000..b9c7363c72 --- /dev/null +++ b/client/src/components/Home/HomeGridEpochResults/HomeGridEpochResults.tsx @@ -0,0 +1,67 @@ +import cx from 'classnames'; +import _first from 'lodash/first'; +import React, { FC, useState } from 'react'; +import { useTranslation } from 'react-i18next'; + +import GridTile from 'components/shared/Grid/GridTile'; +import Svg from 'components/ui/Svg'; +import useCurrentEpoch from 'hooks/queries/useCurrentEpoch'; +import useIsDecisionWindowOpen from 'hooks/queries/useIsDecisionWindowOpen'; +import { arrowRight } from 'svg/misc'; + +import EpochResults from './EpochResults'; +import styles from './HomeGridEpochResults.module.scss'; +import HomeGridEpochResultsProps from './types'; + +const HomeGridEpochResults: FC = ({ className }) => { + const { data: currentEpoch } = useCurrentEpoch(); + const { data: isDecisionWindowOpen } = useIsDecisionWindowOpen(); + const [epoch, setEpoch] = useState(currentEpoch! - 1); + const { t } = useTranslation('translation', { + keyPrefix: 'components.home.homeGridEpochResults', + }); + + const isRightArrowDisabled = epoch === currentEpoch! - 1; + const isLeftArrowDisabled = epoch < 2; + + return ( + +
{ + if (isLeftArrowDisabled) { + return; + } + setEpoch(prev => prev - 1); + }} + > + +
+
{ + if (isRightArrowDisabled) { + return; + } + setEpoch(prev => prev + 1); + }} + > + +
+
+ } + > +
+ +
+ + ); +}; + +export default HomeGridEpochResults; diff --git a/client/src/components/Home/HomeGridEpochResults/index.tsx b/client/src/components/Home/HomeGridEpochResults/index.tsx new file mode 100644 index 0000000000..ef38dc65b4 --- /dev/null +++ b/client/src/components/Home/HomeGridEpochResults/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './HomeGridEpochResults'; diff --git a/client/src/components/Home/HomeGridEpochResults/types.ts b/client/src/components/Home/HomeGridEpochResults/types.ts new file mode 100644 index 0000000000..b1b0054ced --- /dev/null +++ b/client/src/components/Home/HomeGridEpochResults/types.ts @@ -0,0 +1,3 @@ +export default interface HomeGridEpochResultsProps { + className?: string; +} diff --git a/client/src/constants/domElementsIds.ts b/client/src/constants/domElementsIds.ts index 72c8b47d5f..3f599d1161 100644 --- a/client/src/constants/domElementsIds.ts +++ b/client/src/constants/domElementsIds.ts @@ -2,3 +2,4 @@ export const METRICS_EPOCH_ID = 'MetricsEpoch'; export const METRICS_GENERAL_ID = 'MetricsGeneral'; export const METRICS_PERSONAL_ID = 'MetricsPersonal'; export const LAYOUT_BODY_ID = 'LayoutBody'; +export const EPOCH_RESULTS_BAR_ID = 'EpochResultsBar'; diff --git a/client/src/locales/en/translation.json b/client/src/locales/en/translation.json index 5bf4807fa7..84dc5e9398 100644 --- a/client/src/locales/en/translation.json +++ b/client/src/locales/en/translation.json @@ -193,6 +193,14 @@ "donationHistory": "Donation history", "noDonationsYet": "No donations yet. Lock GLM
and earn rewards to donate", "edit": "Edit" + }, + "homeGridEpochResults": { + "epochResults": "Epoch {{epoch}} results", + "epochLive": "Epoch {{epoch}} live", + "donations": "Donations", + "matching": "Matching", + "total": "Total", + "clickToVisitProject": "Click to visit project" } }, "settings": { diff --git a/client/src/styles/utils/_colors.scss b/client/src/styles/utils/_colors.scss index e1bb1b6bfd..0f7e628306 100644 --- a/client/src/styles/utils/_colors.scss +++ b/client/src/styles/utils/_colors.scss @@ -1,6 +1,7 @@ $color-black: #000000; $color-black-05: rgba(0, 0, 0, 0.05); $color-black-08: rgba(0, 0, 0, 0.08); +$color-black-10: rgba(0, 0, 0, 0.1); $color-white: #ffffff; $color-octant-dark: #171717; diff --git a/client/src/views/HomeView/HomeView.tsx b/client/src/views/HomeView/HomeView.tsx index 8bad69c2b0..fb7e68b8f5 100644 --- a/client/src/views/HomeView/HomeView.tsx +++ b/client/src/views/HomeView/HomeView.tsx @@ -6,18 +6,14 @@ import HomeGrid from 'components/Home/HomeGrid/HomeGrid'; import HomeRewards from 'components/Home/HomeRewards/HomeRewards'; import ViewTitle from 'components/shared/ViewTitle/ViewTitle'; import useCurrentEpoch from 'hooks/queries/useCurrentEpoch'; -import useIsDecisionWindowOpen from 'hooks/queries/useIsDecisionWindowOpen'; const HomeView = (): ReactElement => { const { t } = useTranslation('translation', { keyPrefix: 'views.home' }); - const { data: isDecisionWindowOpen } = useIsDecisionWindowOpen(); const { data: currentEpoch } = useCurrentEpoch(); return ( <> - - {t('title', { epoch: isDecisionWindowOpen ? currentEpoch : currentEpoch! - 1 })} - + {t('title', { epoch: currentEpoch! - 1 })} From ba0958ab84444038f475ffce80a2f787979f1073 Mon Sep 17 00:00:00 2001 From: Mateusz Stolecki Date: Tue, 10 Sep 2024 10:41:46 +0200 Subject: [PATCH 071/241] Uncommented assertions for budget estimations --- backend/tests/api-e2e/test_api_rewards.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/backend/tests/api-e2e/test_api_rewards.py b/backend/tests/api-e2e/test_api_rewards.py index a37741a762..58fd8fad8d 100644 --- a/backend/tests/api-e2e/test_api_rewards.py +++ b/backend/tests/api-e2e/test_api_rewards.py @@ -70,25 +70,24 @@ def test_rewards_basic( assert res["epoch"] == STARTING_EPOCH # get estimated budget by number of epochs - # TODO uncomment assertions after fix of OCT-1933 - res = client.get_estimated_budget_by_epochs(1, 10000) + res = client.get_estimated_budget_by_epochs(1, 10000000000000000000000) one_epoch_budget_estimation = int(res["budget"]) - # assert one_epoch_budget_estimation > 0 + assert one_epoch_budget_estimation > 0 - res = client.get_estimated_budget_by_epochs(2, 10000) + res = client.get_estimated_budget_by_epochs(2, 10000000000000000000000) two_epochs_budget_estimation = int(res["budget"]) - # assert two_epochs_budget_estimation > 0 - # assert two_epochs_budget_estimation > one_epoch_budget_estimation + assert two_epochs_budget_estimation > 0 + assert two_epochs_budget_estimation > one_epoch_budget_estimation # get estimated budget by number of days - res = client.get_estimated_budget_by_days(200, 10000) + res = client.get_estimated_budget_by_days(200, 10000000000000000000000) two_hundreds_days_budget_estimation = int(res["budget"]) - # assert two_hundreds_days_budget_estimation > 0 + assert two_hundreds_days_budget_estimation > 0 - res = client.get_estimated_budget_by_days(300, 10000) + res = client.get_estimated_budget_by_days(300, 10000000000000000000000) three_hundreds_days_budget_estimation = int(res["budget"]) - # assert three_hundreds_days_budget_estimation > 0 - # assert three_hundreds_days_budget_estimation > two_hundreds_days_budget_estimation + assert three_hundreds_days_budget_estimation > 0 + assert three_hundreds_days_budget_estimation > two_hundreds_days_budget_estimation # write merkle root for withdrawals vault_core.confirm_withdrawals() From fa2700f14e050aad4f36565d04cb4262b195993a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Zi=C3=B3=C5=82ek?= Date: Tue, 10 Sep 2024 12:53:43 +0200 Subject: [PATCH 072/241] feat: different order for mobile & desktop --- .../LayoutFooter/LayoutFooter.module.scss | 6 --- .../Layout/LayoutFooter/LayoutFooter.tsx | 41 +++++++++++-------- 2 files changed, 23 insertions(+), 24 deletions(-) diff --git a/client/src/components/shared/Layout/LayoutFooter/LayoutFooter.module.scss b/client/src/components/shared/Layout/LayoutFooter/LayoutFooter.module.scss index cdccb18f15..45286af756 100644 --- a/client/src/components/shared/Layout/LayoutFooter/LayoutFooter.module.scss +++ b/client/src/components/shared/Layout/LayoutFooter/LayoutFooter.module.scss @@ -67,12 +67,6 @@ font-size: $font-size-14; font-weight: $font-weight-semibold; color: $color-octant-grey5; - - @media #{$tablet-down} { - &:not(.isVisibleAtLowerWidth) { - display: none; - } - } } } diff --git a/client/src/components/shared/Layout/LayoutFooter/LayoutFooter.tsx b/client/src/components/shared/Layout/LayoutFooter/LayoutFooter.tsx index e14a896c50..10e62e83db 100644 --- a/client/src/components/shared/Layout/LayoutFooter/LayoutFooter.tsx +++ b/client/src/components/shared/Layout/LayoutFooter/LayoutFooter.tsx @@ -14,6 +14,7 @@ import { TERMS_OF_USE, TWITTER_LINK, } from 'constants/urls'; +import useMediaQuery from 'hooks/helpers/useMediaQuery'; import { octantSemiTransparent } from 'svg/logo'; import styles from './LayoutFooter.module.scss'; @@ -21,17 +22,27 @@ import LayoutFooterProps from './types'; const LayoutFooter: FC = ({ className }) => { const { t } = useTranslation('translation', { keyPrefix: 'layout.footer' }); + const { isDesktop } = useMediaQuery(); - const links = [ - { label: t('links.website'), link: OCTANT_BUILD_LINK }, - { label: t('links.docs'), link: OCTANT_DOCS }, - { isVisibleAtLowerWidth: false, label: t('links.blog'), link: BLOG_POST }, - { label: t('links.discord'), link: DISCORD_LINK }, - { label: t('links.farcaster'), link: FARCASTER_LINK }, - { label: t('links.twitterX'), link: TWITTER_LINK }, - { isVisibleAtLowerWidth: false, label: t('links.brandAssets'), link: BRAND_ASSETS_FIGMA_LINK }, - { label: t('links.termsOfUse'), link: TERMS_OF_USE }, - ]; + const links = isDesktop + ? [ + { label: t('links.website'), link: OCTANT_BUILD_LINK }, + { label: t('links.docs'), link: OCTANT_DOCS }, + { label: t('links.blog'), link: BLOG_POST }, + { label: t('links.discord'), link: DISCORD_LINK }, + { label: t('links.farcaster'), link: FARCASTER_LINK }, + { label: t('links.twitterX'), link: TWITTER_LINK }, + { label: t('links.brandAssets'), link: BRAND_ASSETS_FIGMA_LINK }, + { label: t('links.termsOfUse'), link: TERMS_OF_USE }, + ] + : [ + { label: t('links.website'), link: OCTANT_BUILD_LINK }, + { label: t('links.docs'), link: OCTANT_DOCS }, + { label: t('links.farcaster'), link: FARCASTER_LINK }, + { label: t('links.twitterX'), link: TWITTER_LINK }, + { label: t('links.discord'), link: DISCORD_LINK }, + { label: t('links.termsOfUse'), link: TERMS_OF_USE }, + ]; return (
@@ -53,14 +64,8 @@ const LayoutFooter: FC = ({ className }) => {
- {links.map(({ link, label, isVisibleAtLowerWidth = true }) => ( - + {links.map(({ link, label }) => ( + {`→ ${label}`} ))} From a37b778f818252390797da895c8b1a27295a26c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Miko=C5=82ajczyk?= Date: Tue, 10 Sep 2024 12:55:12 +0200 Subject: [PATCH 073/241] oct-1874: absoulte paths --- .../CalculatingUQScore/CalculatingUQScore.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/client/src/components/Home/HomeGridUQScore/ModalCalculatingUQScore/CalculatingUQScore/CalculatingUQScore.tsx b/client/src/components/Home/HomeGridUQScore/ModalCalculatingUQScore/CalculatingUQScore/CalculatingUQScore.tsx index ea7062391d..4f73ab052f 100644 --- a/client/src/components/Home/HomeGridUQScore/ModalCalculatingUQScore/CalculatingUQScore/CalculatingUQScore.tsx +++ b/client/src/components/Home/HomeGridUQScore/ModalCalculatingUQScore/CalculatingUQScore/CalculatingUQScore.tsx @@ -3,6 +3,8 @@ import React, { FC, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useAccount, useSignMessage } from 'wagmi'; +import AddressScore from 'components/Home/HomeGridUQScore/AddressScore'; +import ProgressPath from 'components/Home/HomeGridUQScore/ProgressPath'; import BoxRounded from 'components/ui/BoxRounded'; import Svg from 'components/ui/Svg'; import { DELEGATION_MIN_SCORE } from 'constants/delegation'; @@ -17,9 +19,6 @@ import { notificationIconWarning } from 'svg/misc'; import styles from './CalculatingUQScore.module.scss'; import CalculatingUQScoreProps from './types'; -import AddressScore from '../../AddressScore'; -import ProgressPath from '../../ProgressPath'; - const CalculatingUQScore: FC = ({ setShowCloseButton }) => { const { t } = useTranslation('translation', { keyPrefix: 'components.home.homeGridUQScore.modalCalculatingUQScore', From b0ca6c5a0227c600c8751e0601f3ffeca4c49962 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Miko=C5=82ajczyk?= Date: Tue, 10 Sep 2024 13:51:26 +0200 Subject: [PATCH 074/241] oct-1876: data-test fix --- .../TransactionDetailsRest/TransactionDetailsRest.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/src/components/Home/HomeGridTransactions/ModalTransactionDetails/TransactionDetails/TransactionDetailsRest/TransactionDetailsRest.tsx b/client/src/components/Home/HomeGridTransactions/ModalTransactionDetails/TransactionDetails/TransactionDetailsRest/TransactionDetailsRest.tsx index df485a9220..7483659b67 100644 --- a/client/src/components/Home/HomeGridTransactions/ModalTransactionDetails/TransactionDetails/TransactionDetailsRest/TransactionDetailsRest.tsx +++ b/client/src/components/Home/HomeGridTransactions/ModalTransactionDetails/TransactionDetails/TransactionDetailsRest/TransactionDetailsRest.tsx @@ -34,12 +34,12 @@ const TransactionDetailsRest: FC = ({ const sections: SectionProps[] = [ { dataTest: isPatronDonation - ? 'EarnHistoryItemDetailsRest__matchingFundDonation' - : 'EarnHistoryItemDetailsRest__amount', + ? 'TransactionDetailsRest__matchingFundDonation' + : 'TransactionDetailsRest__amount', doubleValueProps: { dataTest: isPatronDonation - ? 'EarnHistoryItemDetailsRest__matchingFundDonation__DoubleValue' - : 'EarnHistoryItemDetailsRest__amount__DoubleValue', + ? 'TransactionDetailsRest__matchingFundDonation__DoubleValue' + : 'TransactionDetailsRest__amount__DoubleValue', valueCrypto: eventData.amount, ...(['withdrawal', 'patron_mode_donation'].includes(type) ? { cryptoCurrency: 'ethereum', getFormattedEthValueProps: { shouldIgnoreGwei: true } } From 677ce5bb9c3880e68c35e981ce0adf6c10360415 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Miko=C5=82ajczyk?= Date: Tue, 10 Sep 2024 15:43:34 +0200 Subject: [PATCH 075/241] oct-1890: cr fixes --- client/src/components/Settings/Settings.tsx | 4 +- .../LayoutConnectWallet.tsx | 4 +- .../ModalLayoutConnectWallet.tsx | 4 +- .../src/hooks/helpers/useAppConnectManager.ts | 11 ++- client/src/hooks/helpers/useAppIsLoading.ts | 7 +- client/src/locales/en/translation.json | 5 +- client/src/store/settings/store.ts | 78 +------------------ client/src/store/settings/types.ts | 20 ----- 8 files changed, 26 insertions(+), 107 deletions(-) diff --git a/client/src/components/Settings/Settings.tsx b/client/src/components/Settings/Settings.tsx index de4f5914e1..eef65d368d 100644 --- a/client/src/components/Settings/Settings.tsx +++ b/client/src/components/Settings/Settings.tsx @@ -1,4 +1,5 @@ import React, { ReactElement } from 'react'; +import { useTranslation } from 'react-i18next'; import { useAccount } from 'wagmi'; import SettingsCryptoMainValueBox from 'components/Settings/SettingsCryptoMainValueBox'; @@ -12,13 +13,14 @@ import useIsProjectAdminMode from 'hooks/helpers/useIsProjectAdminMode'; import styles from './Settings.module.scss'; const Settings = (): ReactElement => { + const { t } = useTranslation('translation', { keyPrefix: 'components.settings' }); const { isConnected } = useAccount(); const isProjectAdminMode = useIsProjectAdminMode(); return (
-
Settings
+
{t('title')}
{!isProjectAdminMode && (
diff --git a/client/src/components/shared/Layout/LayoutConnectWallet/LayoutConnectWallet.tsx b/client/src/components/shared/Layout/LayoutConnectWallet/LayoutConnectWallet.tsx index 0d64324525..92250331f5 100644 --- a/client/src/components/shared/Layout/LayoutConnectWallet/LayoutConnectWallet.tsx +++ b/client/src/components/shared/Layout/LayoutConnectWallet/LayoutConnectWallet.tsx @@ -8,7 +8,7 @@ import BoxRounded from 'components/ui/BoxRounded'; import Loader from 'components/ui/Loader'; import Svg from 'components/ui/Svg'; import networkConfig from 'constants/networkConfig'; -import useSettingsStore from 'store/settings/store'; +import useDelegationStore from 'store/delegation/store'; import { browserWallet, walletConnect, ledgerConnect } from 'svg/wallet'; import styles from './LayoutConnectWallet.module.scss'; @@ -18,7 +18,7 @@ const LayoutConnectWallet: FC = () => { keyPrefix: 'components.dedicated.connectWallet', }); - const { isDelegationInProgress, setIsDelegationConnectModalOpen } = useSettingsStore(state => ({ + const { isDelegationInProgress, setIsDelegationConnectModalOpen } = useDelegationStore(state => ({ isDelegationInProgress: state.data.isDelegationInProgress, setIsDelegationConnectModalOpen: state.setIsDelegationConnectModalOpen, })); diff --git a/client/src/components/shared/Layout/ModalLayoutConnectWallet/ModalLayoutConnectWallet.tsx b/client/src/components/shared/Layout/ModalLayoutConnectWallet/ModalLayoutConnectWallet.tsx index 87be9e24c6..79e15dbd57 100644 --- a/client/src/components/shared/Layout/ModalLayoutConnectWallet/ModalLayoutConnectWallet.tsx +++ b/client/src/components/shared/Layout/ModalLayoutConnectWallet/ModalLayoutConnectWallet.tsx @@ -4,7 +4,7 @@ import { useAccount } from 'wagmi'; import LayoutConnectWallet from 'components/shared/Layout/LayoutConnectWallet'; import Modal from 'components/ui/Modal'; -import useSettingsStore from 'store/settings/store'; +import useDelegationStore from 'store/delegation/store'; import ModalLayoutConnectWalletProps from './types'; @@ -12,7 +12,7 @@ const ModalLayoutConnectWallet: FC = ({ modalProp const { t } = useTranslation('translation', { keyPrefix: 'components.dedicated.modalConnectWallet', }); - const { isDelegationConnectModalOpen, setIsDelegationConnectModalOpen } = useSettingsStore( + const { isDelegationConnectModalOpen, setIsDelegationConnectModalOpen } = useDelegationStore( state => ({ isDelegationConnectModalOpen: state.data.isDelegationConnectModalOpen, setIsDelegationConnectModalOpen: state.setIsDelegationConnectModalOpen, diff --git a/client/src/hooks/helpers/useAppConnectManager.ts b/client/src/hooks/helpers/useAppConnectManager.ts index 792f6513ca..4e79d41c24 100644 --- a/client/src/hooks/helpers/useAppConnectManager.ts +++ b/client/src/hooks/helpers/useAppConnectManager.ts @@ -12,6 +12,7 @@ import useSyncStatus, { Response } from 'hooks/queries/useSyncStatus'; import localStorageService, { LocalStorageInitParams } from 'services/localStorageService'; import toastService from 'services/toastService'; import useAllocationsStore from 'store/allocations/store'; +import useDelegationStore from 'store/delegation/store'; import useOnboardingStore from 'store/onboarding/store'; import useSettingsStore from 'store/settings/store'; import useTipsStore from 'store/tips/store'; @@ -60,8 +61,13 @@ export default function useAppConnectManager( const { setValuesFromLocalStorage: setValuesFromLocalStorageTips } = useTipsStore(state => ({ setValuesFromLocalStorage: state.setValuesFromLocalStorage, })); - const { setValuesFromLocalStorage: setValuesFromLocalStorageSettings, isDelegationInProgress } = - useSettingsStore(state => ({ + const { setValuesFromLocalStorage: setValuesFromLocalStorageSettings } = useSettingsStore( + state => ({ + setValuesFromLocalStorage: state.setValuesFromLocalStorage, + }), + ); + const { setValuesFromLocalStorage: setValuesFromLocalStorageDelegation, isDelegationInProgress } = + useDelegationStore(state => ({ isDelegationInProgress: state.data.isDelegationInProgress, setValuesFromLocalStorage: state.setValuesFromLocalStorage, })); @@ -90,6 +96,7 @@ export default function useAppConnectManager( // Store is populated with data from LS, hence init here. localStorageService.init(localStorageInitParams as LocalStorageInitParams); setValuesFromLocalStorageSettings(); + setValuesFromLocalStorageDelegation(); setValuesFromLocalStorageOnboarding(); setValuesFromLocalStorageTips(); diff --git a/client/src/hooks/helpers/useAppIsLoading.ts b/client/src/hooks/helpers/useAppIsLoading.ts index d06334002d..9c853c4a9e 100644 --- a/client/src/hooks/helpers/useAppIsLoading.ts +++ b/client/src/hooks/helpers/useAppIsLoading.ts @@ -4,6 +4,7 @@ import useIsPatronMode from 'hooks/queries/useIsPatronMode'; import useUserTOS from 'hooks/queries/useUserTOS'; import useAllProjects from 'hooks/subgraph/useAllProjects'; import useAllocationsStore from 'store/allocations/store'; +import useDelegationStore from 'store/delegation/store'; import useOnboardingStore from 'store/onboarding/store'; import useSettingsStore from 'store/settings/store'; import useTipsStore from 'store/tips/store'; @@ -19,7 +20,10 @@ export default function useAppIsLoading(isFlushRequired: boolean): boolean { const { isInitialized: isOnboardingInitialized } = useOnboardingStore(state => ({ isInitialized: state.meta.isInitialized, })); - const { isInitialized: isSettingsInitialized, isDelegationInProgress } = useSettingsStore( + const { isInitialized: isSettingsInitialized } = useSettingsStore(state => ({ + isInitialized: state.meta.isInitialized, + })); + const { isInitialized: isDelegationInitialized, isDelegationInProgress } = useDelegationStore( state => ({ isDelegationInProgress: state.data.isDelegationInProgress, isInitialized: state.meta.isInitialized, @@ -42,6 +46,7 @@ export default function useAppIsLoading(isFlushRequired: boolean): boolean { (!isPreLaunch && !isAllocationsInitialized) || !isOnboardingInitialized || !isSettingsInitialized || + !isDelegationInitialized || isFlushRequired || !isTipsStoreInitialized || (isFetchingUserTOS && !isRefetchingUserTOS) || diff --git a/client/src/locales/en/translation.json b/client/src/locales/en/translation.json index e4c97d5be9..7ea165e89e 100644 --- a/client/src/locales/en/translation.json +++ b/client/src/locales/en/translation.json @@ -199,6 +199,7 @@ } }, "settings": { + "title": "Settings", "patronMode": { "enablePatronMode": "Enable patron mode", "disablePatronMode": "Disable patron mode", @@ -420,7 +421,7 @@ "docs": "Docs", "brandAssets": "Brand assets", "termsOfUse": "Terms of use", - "farcaster": "Farcaster", + "farcaster": "Farcaster", "twitterX": "Twitter/X" } }, @@ -706,4 +707,4 @@ "information": "We're synchronizing things to prepare the
next epoch, so the app will be unavailable
for a little while. Please check back soon." } } -} +} \ No newline at end of file diff --git a/client/src/store/settings/store.ts b/client/src/store/settings/store.ts index a387afb0ed..8a28ef5cfb 100644 --- a/client/src/store/settings/store.ts +++ b/client/src/store/settings/store.ts @@ -3,11 +3,6 @@ import { IS_CRYPTO_MAIN_VALUE_DISPLAY, IS_ONBOARDING_ALWAYS_VISIBLE, ARE_OCTANT_TIPS_ALWAYS_VISIBLE, - DELEGATION_PRIMARY_ADDRESS, - DELEGATION_SECONDARY_ADDRESS, - IS_DELEGATION_COMPLETED, - PRIMARY_ADDRESS_SCORE, - SECONDARY_ADDRESS_SCORE, } from 'constants/localStorageKeys'; import { getStoreWithMeta } from 'store/utils/getStoreWithMeta'; @@ -15,18 +10,9 @@ import { SettingsData, SettingsMethods } from './types'; export const initialState: SettingsData = { areOctantTipsAlwaysVisible: false, - calculatingUQScoreMode: 'score', - delegationPrimaryAddress: undefined, - delegationSecondaryAddress: undefined, displayCurrency: 'usd', isAllocateOnboardingAlwaysVisible: false, isCryptoMainValueDisplay: true, - isDelegationCalculatingUQScoreModalOpen: false, - isDelegationCompleted: false, - isDelegationConnectModalOpen: false, - isDelegationInProgress: false, - primaryAddressScore: undefined, - secondaryAddressScore: undefined, }; export default getStoreWithMeta({ @@ -38,23 +24,6 @@ export default getStoreWithMeta({ set(state => ({ data: { ...state.data, areOctantTipsAlwaysVisible: payload } })); }, - // eslint-disable-next-line @typescript-eslint/naming-convention - setCalculatingUQScoreMode: payload => { - set(state => ({ data: { ...state.data, calculatingUQScoreMode: payload } })); - }, - - // eslint-disable-next-line @typescript-eslint/naming-convention - setDelegationPrimaryAddress: payload => { - localStorage.setItem(DELEGATION_PRIMARY_ADDRESS, JSON.stringify(payload)); - set(state => ({ data: { ...state.data, delegationPrimaryAddress: payload } })); - }, - - // eslint-disable-next-line @typescript-eslint/naming-convention - setDelegationSecondaryAddress: payload => { - localStorage.setItem(DELEGATION_SECONDARY_ADDRESS, JSON.stringify(payload)); - set(state => ({ data: { ...state.data, delegationSecondaryAddress: payload } })); - }, - // eslint-disable-next-line @typescript-eslint/naming-convention setDisplayCurrency: payload => { localStorage.setItem(DISPLAY_CURRENCY, JSON.stringify(payload)); @@ -73,39 +42,6 @@ export default getStoreWithMeta({ set(state => ({ data: { ...state.data, isCryptoMainValueDisplay: payload } })); }, - // eslint-disable-next-line @typescript-eslint/naming-convention - setIsDelegationCalculatingUQScoreModalOpen: payload => { - set(state => ({ data: { ...state.data, isDelegationCalculatingUQScoreModalOpen: payload } })); - }, - - // eslint-disable-next-line @typescript-eslint/naming-convention - setIsDelegationCompleted: payload => { - localStorage.setItem(IS_DELEGATION_COMPLETED, JSON.stringify(payload)); - set(state => ({ data: { ...state.data, isDelegationCompleted: payload } })); - }, - - // eslint-disable-next-line @typescript-eslint/naming-convention - setIsDelegationConnectModalOpen: payload => { - set(state => ({ data: { ...state.data, isDelegationConnectModalOpen: payload } })); - }, - - // eslint-disable-next-line @typescript-eslint/naming-convention - setIsDelegationInProgress: payload => { - set(state => ({ data: { ...state.data, isDelegationInProgress: payload } })); - }, - - // eslint-disable-next-line @typescript-eslint/naming-convention - setPrimaryAddressScore: payload => { - localStorage.setItem(PRIMARY_ADDRESS_SCORE, JSON.stringify(payload)); - set(state => ({ data: { ...state.data, primaryAddressScore: payload } })); - }, - - // eslint-disable-next-line @typescript-eslint/naming-convention - setSecondaryAddressScore: payload => { - localStorage.setItem(SECONDARY_ADDRESS_SCORE, JSON.stringify(payload)); - set(state => ({ data: { ...state.data, secondaryAddressScore: payload } })); - }, - setValuesFromLocalStorage: () => set({ data: { @@ -113,12 +49,7 @@ export default getStoreWithMeta({ areOctantTipsAlwaysVisible: JSON.parse( localStorage.getItem(ARE_OCTANT_TIPS_ALWAYS_VISIBLE) || 'null', ), - delegationPrimaryAddress: JSON.parse( - localStorage.getItem(DELEGATION_PRIMARY_ADDRESS) || 'null', - ), - delegationSecondaryAddress: JSON.parse( - localStorage.getItem(DELEGATION_SECONDARY_ADDRESS) || 'null', - ), + displayCurrency: JSON.parse(localStorage.getItem(DISPLAY_CURRENCY) || 'null'), isAllocateOnboardingAlwaysVisible: JSON.parse( localStorage.getItem(IS_ONBOARDING_ALWAYS_VISIBLE) || 'null', @@ -126,13 +57,6 @@ export default getStoreWithMeta({ isCryptoMainValueDisplay: JSON.parse( localStorage.getItem(IS_CRYPTO_MAIN_VALUE_DISPLAY) || 'null', ), - isDelegationCompleted: JSON.parse( - localStorage.getItem(IS_DELEGATION_COMPLETED) || 'false', - ), - primaryAddressScore: JSON.parse(localStorage.getItem(PRIMARY_ADDRESS_SCORE) || 'null'), - secondaryAddressScore: JSON.parse( - localStorage.getItem(SECONDARY_ADDRESS_SCORE) || 'null', - ), }, meta: { isInitialized: true, diff --git a/client/src/store/settings/types.ts b/client/src/store/settings/types.ts index 7a120d6239..c30c5e9824 100644 --- a/client/src/store/settings/types.ts +++ b/client/src/store/settings/types.ts @@ -1,37 +1,17 @@ export interface SettingsData { areOctantTipsAlwaysVisible: boolean; - calculatingUQScoreMode: 'score' | 'sign'; - delegationPrimaryAddress?: string; - delegationSecondaryAddress?: string; displayCurrency: 'usd' | 'aud' | 'eur' | 'jpy' | 'cny' | 'gbp'; isAllocateOnboardingAlwaysVisible: boolean; isCryptoMainValueDisplay: boolean; - isDelegationCalculatingUQScoreModalOpen: boolean; - isDelegationCompleted: boolean; - isDelegationConnectModalOpen: boolean; - isDelegationInProgress: boolean; - primaryAddressScore?: number; - secondaryAddressScore?: number; } export interface SettingsMethods { reset: () => void; setAreOctantTipsAlwaysVisible: (payload: SettingsData['areOctantTipsAlwaysVisible']) => void; - setCalculatingUQScoreMode: (payload: SettingsData['calculatingUQScoreMode']) => void; - setDelegationPrimaryAddress: (payload: SettingsData['delegationPrimaryAddress']) => void; - setDelegationSecondaryAddress: (payload: SettingsData['delegationSecondaryAddress']) => void; setDisplayCurrency: (payload: SettingsData['displayCurrency']) => void; setIsAllocateOnboardingAlwaysVisible: ( payload: SettingsData['isAllocateOnboardingAlwaysVisible'], ) => void; setIsCryptoMainValueDisplay: (payload: SettingsData['isCryptoMainValueDisplay']) => void; - setIsDelegationCalculatingUQScoreModalOpen: ( - payload: SettingsData['isDelegationCalculatingUQScoreModalOpen'], - ) => void; - setIsDelegationCompleted: (payload: SettingsData['isDelegationCompleted']) => void; - setIsDelegationConnectModalOpen: (payload: SettingsData['isDelegationConnectModalOpen']) => void; - setIsDelegationInProgress: (payload: SettingsData['isDelegationInProgress']) => void; - setPrimaryAddressScore: (payload: SettingsData['primaryAddressScore']) => void; - setSecondaryAddressScore: (payload: SettingsData['secondaryAddressScore']) => void; setValuesFromLocalStorage: () => void; } From cfc2dba6c87c0dc5094812104543e35c6533e6e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Zi=C3=B3=C5=82ek?= Date: Wed, 11 Sep 2024 10:59:51 +0200 Subject: [PATCH 076/241] feat: projects search input in place --- .../ProjectsList/ProjectsList.module.scss | 8 +++++ .../Projects/ProjectsList/ProjectsList.tsx | 14 +-------- .../ProjectsView/ProjectsView.module.scss | 8 ++++- .../src/views/ProjectsView/ProjectsView.tsx | 29 ++++++++++++++++++- 4 files changed, 44 insertions(+), 15 deletions(-) diff --git a/client/src/components/Projects/ProjectsList/ProjectsList.module.scss b/client/src/components/Projects/ProjectsList/ProjectsList.module.scss index ba476e4a0b..be7cd80531 100644 --- a/client/src/components/Projects/ProjectsList/ProjectsList.module.scss +++ b/client/src/components/Projects/ProjectsList/ProjectsList.module.scss @@ -14,8 +14,16 @@ $elementMargin: 1.6rem; } } +<<<<<<< Updated upstream .inputSearch { margin-bottom: $elementMargin; +======= +.list { + display: flex; + flex-wrap: wrap; + width: 100%; + justify-content: space-between; +>>>>>>> Stashed changes } .element { diff --git a/client/src/components/Projects/ProjectsList/ProjectsList.tsx b/client/src/components/Projects/ProjectsList/ProjectsList.tsx index ae452c24b3..85d3ff3c45 100644 --- a/client/src/components/Projects/ProjectsList/ProjectsList.tsx +++ b/client/src/components/Projects/ProjectsList/ProjectsList.tsx @@ -91,19 +91,7 @@ const ProjectsList: FC = ({
- )} - {isLatestEpochAndDecisionWindowOpen && ( - } - onChange={onChangeSearchQuery} - onClear={() => setSearchQuery('')} - placeholder={t('searchInputPlaceholder')} - value={searchQuery} - variant="search" - /> - )} + )}z {isLatestEpochAndDecisionWindowOpen && !isFetchingProjectsWithRewards && projectsIpfsWithRewardsFiltered.length === 0 && ( diff --git a/client/src/views/ProjectsView/ProjectsView.module.scss b/client/src/views/ProjectsView/ProjectsView.module.scss index d4ce65ebf2..6e08e71172 100644 --- a/client/src/views/ProjectsView/ProjectsView.module.scss +++ b/client/src/views/ProjectsView/ProjectsView.module.scss @@ -1,5 +1,7 @@ +$elementMargin: $elementMargin; + .tip { - margin-bottom: 1.6rem; + margin-bottom: $elementMargin; @media #{$desktop-up} { margin-bottom: 2.8rem; @@ -11,6 +13,10 @@ justify-content: center; } +.inputSearch { + margin-bottom: $elementMargin; +} + .archives { width: 100%; } diff --git a/client/src/views/ProjectsView/ProjectsView.tsx b/client/src/views/ProjectsView/ProjectsView.tsx index 21e595e773..589280bb0f 100644 --- a/client/src/views/ProjectsView/ProjectsView.tsx +++ b/client/src/views/ProjectsView/ProjectsView.tsx @@ -1,4 +1,11 @@ -import React, { ReactElement, useState, useMemo, useLayoutEffect, useEffect } from 'react'; +import React, { + ReactElement, + useState, + useMemo, + useLayoutEffect, + useEffect, + ChangeEvent, +} from 'react'; import { useTranslation } from 'react-i18next'; import InfiniteScroll from 'react-infinite-scroller'; @@ -18,12 +25,18 @@ import useIsDecisionWindowOpen from 'hooks/queries/useIsDecisionWindowOpen'; import useTipsStore from 'store/tips/store'; import styles from './ProjectsView.module.scss'; +import InputText from '../../components/ui/InputText'; +import Svg from '../../components/ui/Svg'; +import { magnifyingGlass } from '../../svg/misc.ts'; const ProjectsView = (): ReactElement => { const { isDesktop } = useMediaQuery(); const { t } = useTranslation('translation', { keyPrefix: 'views.projects', }); + + const [searchQuery, setSearchQuery] = useState(''); + const { data: currentEpoch } = useCurrentEpoch(); const { data: isDecisionWindowOpen } = useIsDecisionWindowOpen({ refetchOnMount: true, @@ -45,6 +58,10 @@ const ProjectsView = (): ReactElement => { return projectsLoadedArchivedEpochsNumber ?? 0; }); + const onChangeSearchQuery = (e: ChangeEvent): void => { + setSearchQuery(e.target.value); + }; + const isEpoch1 = currentEpoch === 1; const isAddToFavouritesTipVisible = !wasAddFavouritesAlreadyClosed && !isEpoch1; @@ -108,6 +125,16 @@ const ProjectsView = (): ReactElement => { title={t('tip.title')} /> )} + } + onChange={onChangeSearchQuery} + onClear={() => setSearchQuery('')} + placeholder={t('searchInputPlaceholder')} + value={searchQuery} + variant="search" + /> {!areCurrentEpochsProjectsHiddenOutsideAllocationWindow && ( Date: Wed, 11 Sep 2024 14:20:09 +0200 Subject: [PATCH 077/241] style: clean up & lint --- .../Projects/ProjectsList/ProjectsList.module.scss | 5 ----- .../src/components/Projects/ProjectsList/ProjectsList.tsx | 2 +- client/src/views/ProjectsView/ProjectsView.module.scss | 2 +- client/src/views/ProjectsView/ProjectsView.tsx | 6 +++--- 4 files changed, 5 insertions(+), 10 deletions(-) diff --git a/client/src/components/Projects/ProjectsList/ProjectsList.module.scss b/client/src/components/Projects/ProjectsList/ProjectsList.module.scss index be7cd80531..878d424018 100644 --- a/client/src/components/Projects/ProjectsList/ProjectsList.module.scss +++ b/client/src/components/Projects/ProjectsList/ProjectsList.module.scss @@ -14,16 +14,11 @@ $elementMargin: 1.6rem; } } -<<<<<<< Updated upstream -.inputSearch { - margin-bottom: $elementMargin; -======= .list { display: flex; flex-wrap: wrap; width: 100%; justify-content: space-between; ->>>>>>> Stashed changes } .element { diff --git a/client/src/components/Projects/ProjectsList/ProjectsList.tsx b/client/src/components/Projects/ProjectsList/ProjectsList.tsx index 85d3ff3c45..864005c2a9 100644 --- a/client/src/components/Projects/ProjectsList/ProjectsList.tsx +++ b/client/src/components/Projects/ProjectsList/ProjectsList.tsx @@ -91,7 +91,7 @@ const ProjectsList: FC = ({
- )}z + )} {isLatestEpochAndDecisionWindowOpen && !isFetchingProjectsWithRewards && projectsIpfsWithRewardsFiltered.length === 0 && ( diff --git a/client/src/views/ProjectsView/ProjectsView.module.scss b/client/src/views/ProjectsView/ProjectsView.module.scss index 6e08e71172..1d8ae77911 100644 --- a/client/src/views/ProjectsView/ProjectsView.module.scss +++ b/client/src/views/ProjectsView/ProjectsView.module.scss @@ -1,4 +1,4 @@ -$elementMargin: $elementMargin; +$elementMargin: 1.6rem; .tip { margin-bottom: $elementMargin; diff --git a/client/src/views/ProjectsView/ProjectsView.tsx b/client/src/views/ProjectsView/ProjectsView.tsx index 589280bb0f..be4f8e248e 100644 --- a/client/src/views/ProjectsView/ProjectsView.tsx +++ b/client/src/views/ProjectsView/ProjectsView.tsx @@ -23,11 +23,11 @@ import useMediaQuery from 'hooks/helpers/useMediaQuery'; import useCurrentEpoch from 'hooks/queries/useCurrentEpoch'; import useIsDecisionWindowOpen from 'hooks/queries/useIsDecisionWindowOpen'; import useTipsStore from 'store/tips/store'; +import InputText from 'components/ui/InputText'; +import Svg from 'components/ui/Svg'; +import { magnifyingGlass } from 'svg/misc'; import styles from './ProjectsView.module.scss'; -import InputText from '../../components/ui/InputText'; -import Svg from '../../components/ui/Svg'; -import { magnifyingGlass } from '../../svg/misc.ts'; const ProjectsView = (): ReactElement => { const { isDesktop } = useMediaQuery(); From e0a62eda8d16b6352e1e5c38a68ea0772563c4f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Zi=C3=B3=C5=82ek?= Date: Wed, 11 Sep 2024 14:43:41 +0200 Subject: [PATCH 078/241] style: clean up & lint --- .../Projects/ProjectsList/ProjectsList.tsx | 91 ++++--------------- 1 file changed, 17 insertions(+), 74 deletions(-) diff --git a/client/src/components/Projects/ProjectsList/ProjectsList.tsx b/client/src/components/Projects/ProjectsList/ProjectsList.tsx index 864005c2a9..a53514ef96 100644 --- a/client/src/components/Projects/ProjectsList/ProjectsList.tsx +++ b/client/src/components/Projects/ProjectsList/ProjectsList.tsx @@ -1,20 +1,13 @@ import cx from 'classnames'; -import React, { ChangeEvent, FC, memo, useState } from 'react'; +import React, { FC, memo } from 'react'; import { useTranslation } from 'react-i18next'; import ProjectsListItem from 'components/Projects/ProjectsListItem'; import ProjectsListSkeletonItem from 'components/Projects/ProjectsListSkeletonItem'; import Grid from 'components/shared/Grid'; -import Img from 'components/ui/Img'; -import InputText from 'components/ui/InputText/InputText'; -import Svg from 'components/ui/Svg'; -import { PROJECTS_ADDRESSES_RANDOMIZED_ORDER } from 'constants/localStorageKeys'; import useEpochDurationLabel from 'hooks/helpers/useEpochDurationLabel'; -import useIsDecisionWindowOpen from 'hooks/queries/useIsDecisionWindowOpen'; import useProjectsEpoch from 'hooks/queries/useProjectsEpoch'; import useProjectsIpfsWithRewards from 'hooks/queries/useProjectsIpfsWithRewards'; -import { magnifyingGlass } from 'svg/misc'; -import { ProjectsAddressesRandomizedOrder } from 'types/localStorage'; import styles from './ProjectsList.module.scss'; import ProjectsListProps from './types'; @@ -27,44 +20,16 @@ const ProjectsList: FC = ({ const { t } = useTranslation('translation', { keyPrefix: 'components.dedicated.projectsList', }); - const [searchQuery, setSearchQuery] = useState(''); - const { data: isDecisionWindowOpen } = useIsDecisionWindowOpen(); const { data: projectsEpoch, isFetching: isFetchingProjectsEpoch } = useProjectsEpoch(epoch); const { data: projectsIpfsWithRewards, isFetching: isFetchingProjectsWithRewards, - isAnyIpfsError, } = useProjectsIpfsWithRewards(epoch); const epochDurationLabel = useEpochDurationLabel(epoch); const isLoading = isFetchingProjectsEpoch || isFetchingProjectsWithRewards; - const isLatestEpochAndDecisionWindowOpen = epoch === undefined && !!isDecisionWindowOpen; - - const onChangeSearchQuery = (e: ChangeEvent): void => { - setSearchQuery(e.target.value); - }; - - const areProjectsIpfsWithRewardsAvailable = - projectsIpfsWithRewards.length > 0 && !isFetchingProjectsWithRewards && !isAnyIpfsError; - const projectsIpfsWithRewardsFiltered = areProjectsIpfsWithRewardsAvailable - ? projectsIpfsWithRewards.filter(projectIpfsWithRewards => { - return ( - projectIpfsWithRewards.name!.toLowerCase().includes(searchQuery.toLowerCase()) || - projectIpfsWithRewards.address!.toLowerCase().includes(searchQuery.toLowerCase()) - ); - }) - : []; - - const projectsAddressesRandomizedOrder = JSON.parse( - localStorage.getItem(PROJECTS_ADDRESSES_RANDOMIZED_ORDER)!, - ) as ProjectsAddressesRandomizedOrder; - - const projectsAddressesToIterate = isLatestEpochAndDecisionWindowOpen - ? projectsAddressesRandomizedOrder.addressesRandomizedOrder - : projectsIpfsWithRewards.map(({ address }) => address); - return (
= ({
)} - {isLatestEpochAndDecisionWindowOpen && - !isFetchingProjectsWithRewards && - projectsIpfsWithRewardsFiltered.length === 0 && ( -
- - {t('noSearchResults')} -
- )} - {areProjectsIpfsWithRewardsAvailable - ? projectsAddressesToIterate.map((address, index) => { - const projectIpfsWithRewards = projectsIpfsWithRewardsFiltered.find( - element => element.address === address, - ); - - if (!projectIpfsWithRewards) { - return null; + {projectsIpfsWithRewards.length > 0 && !isFetchingProjectsWithRewards + ? projectsIpfsWithRewards.map((projectIpfsWithRewards, index) => ( + - ); - }) + epoch={epoch} + projectIpfsWithRewards={projectIpfsWithRewards} + /> + )) : projectsEpoch?.projectsAddresses?.map((_, index) => ( - // eslint-disable-next-line react/no-array-index-key - - ))} + // eslint-disable-next-line react/no-array-index-key + + ))}
); From b99cbcdb127bb4946aa2e23d2cfda81ec8838488 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Zi=C3=B3=C5=82ek?= Date: Wed, 11 Sep 2024 14:44:00 +0200 Subject: [PATCH 079/241] style: clean up & lint --- client/src/views/ProjectsView/ProjectsView.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/views/ProjectsView/ProjectsView.tsx b/client/src/views/ProjectsView/ProjectsView.tsx index be4f8e248e..f15c912745 100644 --- a/client/src/views/ProjectsView/ProjectsView.tsx +++ b/client/src/views/ProjectsView/ProjectsView.tsx @@ -13,7 +13,9 @@ import ProjectsList from 'components/Projects/ProjectsList'; import ProjectsTimelineWidget from 'components/Projects/ProjectsTimelineWidget'; import Layout from 'components/shared/Layout'; import TipTile from 'components/shared/TipTile'; +import InputText from 'components/ui/InputText'; import Loader from 'components/ui/Loader'; +import Svg from 'components/ui/Svg'; import { WINDOW_PROJECTS_SCROLL_Y, WINDOW_PROJECTS_LOADED_ARCHIVED_EPOCHS_NUMBER, @@ -23,8 +25,6 @@ import useMediaQuery from 'hooks/helpers/useMediaQuery'; import useCurrentEpoch from 'hooks/queries/useCurrentEpoch'; import useIsDecisionWindowOpen from 'hooks/queries/useIsDecisionWindowOpen'; import useTipsStore from 'store/tips/store'; -import InputText from 'components/ui/InputText'; -import Svg from 'components/ui/Svg'; import { magnifyingGlass } from 'svg/misc'; import styles from './ProjectsView.module.scss'; From a0544f6de293aee3e4ca73a708df9cacd8e1dcc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Miko=C5=82ajczyk?= Date: Thu, 12 Sep 2024 09:10:37 +0200 Subject: [PATCH 080/241] oct-1907: allocation drawer --- .../Allocation/Allocation.module.scss | 56 ++ .../src/components/Allocation/Allocation.tsx | 619 ++++++++++++++++++ .../AllocationItem/AllocationItem.module.scss | 7 +- .../AllocationItemRewards.tsx | 34 +- .../AllocationLowUqScore.tsx | 6 +- .../AllocationNavigation.module.scss | 21 + .../AllocationNavigation.tsx | 8 +- .../Allocation/AllocationNavigation/types.ts | 4 - .../AllocationRewardsBox.module.scss | 109 +-- .../AllocationRewardsBox.tsx | 173 +---- .../Allocation/AllocationRewardsBox/types.ts | 4 - .../AllocationSliderBox.module.scss | 87 +++ .../AllocationSliderBox.tsx | 175 +++++ .../Allocation/AllocationSliderBox/index.tsx | 2 + .../Allocation/AllocationSliderBox/types.ts | 8 + .../AllocationSummaryProject.tsx | 2 +- .../AllocationTipTiles/AllocationTipTiles.tsx | 6 +- .../ModalAllocationLowUqScore.tsx | 4 +- client/src/components/Allocation/index.tsx | 2 + client/src/components/Allocation/types.ts | 21 + .../src/components/Allocation/utils.test.ts | 379 +++++++++++ client/src/components/Allocation/utils.ts | 292 +++++++++ .../HomeGridDonations/HomeGridDonations.tsx | 18 +- .../LayoutNavbar/LayoutNavbar.module.scss | 12 +- .../Layout/LayoutNavbar/LayoutNavbar.tsx | 6 +- .../LayoutTopBar/LayoutTopBar.module.scss | 17 + .../Layout/LayoutTopBar/LayoutTopBar.tsx | 91 ++- client/src/constants/domElementsIds.ts | 1 + client/src/locales/en/translation.json | 89 +-- client/src/routes/RootRoutes/RootRoutes.tsx | 12 +- client/src/store/allocations/store.ts | 9 + client/src/store/allocations/types.ts | 4 + client/src/store/layout/store.ts | 6 +- client/src/store/layout/types.ts | 4 +- client/src/svg/misc.ts | 6 + .../views/AllocationView/AllocationView.tsx | 587 +---------------- client/src/views/AllocationView/types.ts | 2 - 37 files changed, 1944 insertions(+), 939 deletions(-) create mode 100644 client/src/components/Allocation/Allocation.module.scss create mode 100644 client/src/components/Allocation/Allocation.tsx create mode 100644 client/src/components/Allocation/AllocationSliderBox/AllocationSliderBox.module.scss create mode 100644 client/src/components/Allocation/AllocationSliderBox/AllocationSliderBox.tsx create mode 100644 client/src/components/Allocation/AllocationSliderBox/index.tsx create mode 100644 client/src/components/Allocation/AllocationSliderBox/types.ts create mode 100644 client/src/components/Allocation/index.tsx create mode 100644 client/src/components/Allocation/types.ts create mode 100644 client/src/components/Allocation/utils.test.ts create mode 100644 client/src/components/Allocation/utils.ts diff --git a/client/src/components/Allocation/Allocation.module.scss b/client/src/components/Allocation/Allocation.module.scss new file mode 100644 index 0000000000..597aec7069 --- /dev/null +++ b/client/src/components/Allocation/Allocation.module.scss @@ -0,0 +1,56 @@ +.root { + padding-bottom: 8.8rem; + width: 100%; + display: flex; + flex-direction: column; + + @media #{$tablet-up} { + padding-bottom: 14.4rem; + } + + @media #{$desktop-up} { + padding-bottom: 0; + width: 68rem; + min-height: 0; + } + + .title { + padding: 0; + display: flex; + align-items: center; + min-height: 8.8rem; + width: 100%; + color: $color-octant-dark; + font-size: $font-size-24; + font-weight: $font-weight-bold; + line-height: 1.4rem; + + @media #{$tablet-up} { + font-size: $font-size-32; + min-height: 14.4rem; + } + + @media #{$desktop-up} { + padding: 0 4rem; + } + } + + .box { + &:not(:last-child) { + margin: 0 auto 1.6rem; + } + } + + .boxesWrapper { + width: 100%; + + &.withMarginBottom { + margin-bottom: 12.8rem; + } + + @media #{$desktop-up} { + padding: 0 4rem 4rem; + overflow: auto; + } + } +} diff --git a/client/src/components/Allocation/Allocation.tsx b/client/src/components/Allocation/Allocation.tsx new file mode 100644 index 0000000000..9af4baf002 --- /dev/null +++ b/client/src/components/Allocation/Allocation.tsx @@ -0,0 +1,619 @@ +import cx from 'classnames'; +import { AnimatePresence } from 'framer-motion'; +import debounce from 'lodash/debounce'; +import isEmpty from 'lodash/isEmpty'; +import React, { ReactElement, useCallback, useEffect, useRef, useState } from 'react'; +import { createPortal } from 'react-dom'; +import { useTranslation } from 'react-i18next'; +import { useAccount } from 'wagmi'; + +import { SignatureOpType, apiGetPendingMultisigSignatures } from 'api/calls/multisigSignatures'; +import AllocationItem from 'components/Allocation/AllocationItem'; +import AllocationItemSkeleton from 'components/Allocation/AllocationItemSkeleton'; +import AllocationNavigation from 'components/Allocation/AllocationNavigation'; +import AllocationRewardsBox from 'components/Allocation/AllocationRewardsBox'; +import AllocationSummary from 'components/Allocation/AllocationSummary'; +import ModalAllocationLowUqScore from 'components/Allocation/ModalAllocationLowUqScore'; +import { LAYOUT_NAVBAR_ID } from 'constants/domElementsIds'; +import useAllocate from 'hooks/events/useAllocate'; +import useAllocationViewSetRewardsForProjects from 'hooks/helpers/useAllocationViewSetRewardsForProjects'; +import useIdsInAllocation from 'hooks/helpers/useIdsInAllocation'; +import useMediaQuery from 'hooks/helpers/useMediaQuery'; +import useAllocateSimulate from 'hooks/mutations/useAllocateSimulate'; +import useCurrentEpoch from 'hooks/queries/useCurrentEpoch'; +import useEpochAllocations from 'hooks/queries/useEpochAllocations'; +import useHistory from 'hooks/queries/useHistory'; +import useIndividualReward from 'hooks/queries/useIndividualReward'; +import useIsContract from 'hooks/queries/useIsContract'; +import useIsDecisionWindowOpen from 'hooks/queries/useIsDecisionWindowOpen'; +import useMatchedProjectRewards from 'hooks/queries/useMatchedProjectRewards'; +import useProjectsEpoch from 'hooks/queries/useProjectsEpoch'; +import useProjectsIpfsWithRewards from 'hooks/queries/useProjectsIpfsWithRewards'; +import useUpcomingBudget from 'hooks/queries/useUpcomingBudget'; +import useUqScore from 'hooks/queries/useUqScore'; +import useUserAllocationNonce from 'hooks/queries/useUserAllocationNonce'; +import useUserAllocations from 'hooks/queries/useUserAllocations'; +import useWithdrawals from 'hooks/queries/useWithdrawals'; +import toastService from 'services/toastService'; +import useAllocationsStore from 'store/allocations/store'; +import { formatUnitsBigInt } from 'utils/formatUnitsBigInt'; +import { parseUnitsBigInt } from 'utils/parseUnitsBigInt'; + +import styles from './Allocation.module.scss'; +import AllocationSliderBox from './AllocationSliderBox'; +import { AllocationValue, AllocationValues, PercentageProportions } from './types'; +import { + getAllocationValuesInitialState, + getAllocationsWithRewards, + getAllocationValuesAfterManualChange, +} from './utils'; + +const Allocation = (): ReactElement => { + const { isConnected } = useAccount(); + const { t } = useTranslation('translation', { keyPrefix: 'components.allocation' }); + const [allocationValues, setAllocationValues] = useState([]); + const [isManualMode, setIsManualMode] = useState(false); + const [addressesWithError, setAddressesWithError] = useState([]); + const [percentageProportions, setPercentageProportions] = useState({}); + const { data: projectsEpoch } = useProjectsEpoch(); + const { data: projectsIpfsWithRewards } = useProjectsIpfsWithRewards(); + const { isRewardsForProjectsSet } = useAllocationViewSetRewardsForProjects(); + const [isWaitingForFirstMultisigSignature, setIsWaitingForFirstMultisigSignature] = + useState(false); + const { + data: allocationSimulated, + mutateAsync: mutateAsyncAllocateSimulate, + isPending: isLoadingAllocateSimulate, + reset: resetAllocateSimulate, + } = useAllocateSimulate(); + const [isWaitingForAllMultisigSignatures, setIsWaitingForAllMultisigSignatures] = useState(false); + const { isFetching: isFetchingUpcomingBudget, isRefetching: isRefetchingUpcomingBudget } = + useUpcomingBudget(); + const { data: isContract } = useIsContract(); + const { address: walletAddress } = useAccount(); + + const navRef = useRef(document.getElementById(LAYOUT_NAVBAR_ID)); + const boxesWrapperRef = useRef(null); + const { data: currentEpoch } = useCurrentEpoch(); + const { refetch: refetchHistory } = useHistory(); + const { + data: userAllocationsOriginal, + isFetching: isFetchingUserAllocation, + refetch: refetchUserAllocations, + } = useUserAllocations(undefined, { refetchOnMount: true }); + + const userAllocations = userAllocationsOriginal && { + ...userAllocationsOriginal, + elements: userAllocationsOriginal.elements.map(element => ({ + ...element, + value: formatUnitsBigInt(element.value), + })), + }; + + const { data: individualReward } = useIndividualReward(); + const { data: isDecisionWindowOpen } = useIsDecisionWindowOpen(); + const { refetch: refetchWithdrawals } = useWithdrawals(); + const { + data: userNonce, + isFetching: isFetchingUserNonce, + refetch: refetchUserAllocationNonce, + } = useUserAllocationNonce(); + const { data: uqScore } = useUqScore(currentEpoch!); + const { refetch: refetchMatchedProjectRewards } = useMatchedProjectRewards(); + const [showLowUQScoreModal, setShowLowUQScoreModal] = useState(false); + const { refetch: refetchEpochAllocations } = useEpochAllocations( + isDecisionWindowOpen ? currentEpoch! - 1 : currentEpoch!, + ); + const { isDesktop } = useMediaQuery(); + + const { + currentView, + setCurrentView, + allocations, + rewardsForProjects, + setAllocations, + addAllocations, + removeAllocations, + setRewardsForProjects, + } = useAllocationsStore(state => ({ + addAllocations: state.addAllocations, + allocations: state.data.allocations, + currentView: state.data.currentView, + removeAllocations: state.removeAllocations, + rewardsForProjects: state.data.rewardsForProjects, + setAllocations: state.setAllocations, + setCurrentView: state.setCurrentView, + setRewardsForProjects: state.setRewardsForProjects, + })); + const { onAddRemoveFromAllocate } = useIdsInAllocation({ + addAllocations, + allocations, + isDecisionWindowOpen, + removeAllocations, + userAllocationsElements: userAllocationsOriginal?.elements, + }); + + const onAllocateSuccess = () => { + toastService.showToast({ + name: 'allocationSuccessful', + title: t('allocationSuccessful'), + }); + refetchMatchedProjectRewards(); + refetchUserAllocations(); + refetchUserAllocationNonce(); + refetchHistory(); + refetchWithdrawals(); + refetchEpochAllocations(); + toastService.hideToast('confirmChanges'); + setAllocations([ + ...allocations.filter(allocation => { + const allocationValue = allocationValues.find(({ address }) => address === allocation); + return allocationValue && allocationValue.value !== '0'; + }), + ]); + setCurrentView('summary'); + }; + + const allocateEvent = useAllocate({ + nonce: userNonce!, + onMultisigMessageSign: () => { + toastService.hideToast('allocationMultisigInitialSignature'); + setIsWaitingForFirstMultisigSignature(false); + setIsWaitingForAllMultisigSignatures(true); + }, + onSuccess: onAllocateSuccess, + }); + + // eslint-disable-next-line react-hooks/exhaustive-deps + const mutateAsyncAllocateSimulateDebounced = useCallback( + debounce( + _allocationValues => { + resetAllocateSimulate(); + mutateAsyncAllocateSimulate(_allocationValues); + }, + 250, + { trailing: true }, + ), + [], + ); + + const setPercentageProportionsWrapper = ( + allocationValuesNew: AllocationValues, + rewardsForProjectsNew: bigint, + ) => { + if (!individualReward) { + return; + } + const percentageProportionsNew = allocationValuesNew.reduce((acc, curr) => { + const valueAsPercentageOfRewardsForProjects = ['0', ''].includes(curr.value) // 0 from the user, empty when removed entirely. + ? '0' + : ( + (parseFloat(curr.value.toString()) * 100) / + parseFloat(formatUnitsBigInt(rewardsForProjectsNew)) + ).toFixed(); + return { + ...acc, + [curr.address]: valueAsPercentageOfRewardsForProjects, + }; + }, {}); + setPercentageProportions(percentageProportionsNew); + }; + + const onResetAllocationValues = ({ + allocationValuesNew = allocationValues, + rewardsForProjectsNew = rewardsForProjects, + shouldReset = false, + } = {}) => { + if ( + isFetchingUserAllocation || + !isRewardsForProjectsSet || + currentEpoch === undefined || + (isConnected && !userAllocations && isDecisionWindowOpen && currentEpoch > 1) + ) { + return; + } + + const userAllocationsAddresses = userAllocations?.elements.map(({ address }) => address); + if (shouldReset && userAllocationsAddresses) { + const userAllocationsAddressesToAdd = userAllocationsAddresses?.filter( + element => !allocations.includes(element), + ); + + userAllocationsAddressesToAdd?.forEach((element, index, array) => { + onAddRemoveFromAllocate(element, [...allocations, ...array.slice(0, index)]); + }); + } + + const allocationValuesNewSum = allocationValuesNew.reduce( + (acc, curr) => acc + parseUnitsBigInt(curr.value), + BigInt(0), + ); + const shouldIsManulModeBeChangedToFalse = allocationValuesNewSum === 0n; + + /** + * Manual needs to be changed to false when values are 0. + * Percentages cant be calculated from 0, equal split cant be maintained, causing the app to crash. + * Mode needs to change to "auto". + */ + if (shouldIsManulModeBeChangedToFalse) { + setIsManualMode(false); + } + + const allocationValuesReset = getAllocationValuesInitialState({ + allocationValues: allocationValuesNew, + allocations: + shouldReset && userAllocationsAddresses + ? [...new Set([...allocations, ...userAllocationsAddresses])] + : allocations, + isManualMode: shouldIsManulModeBeChangedToFalse ? false : isManualMode, + percentageProportions, + rewardsForProjects: rewardsForProjectsNew, + shouldReset, + userAllocationsElements: isDecisionWindowOpen ? userAllocations?.elements || [] : [], + }); + + if (shouldReset) { + const allocationValuesResetSum = allocationValuesReset.reduce( + (acc, curr) => acc + parseUnitsBigInt(curr.value), + BigInt(0), + ); + + setRewardsForProjects(allocationValuesResetSum); + setPercentageProportionsWrapper(allocationValuesReset, allocationValuesResetSum); + + const shouldIsManualModeBeChangedToFalseNew = allocationValuesResetSum === 0n; + if (!shouldIsManualModeBeChangedToFalseNew) { + setIsManualMode(userAllocations!.isManuallyEdited); + } else { + setIsManualMode(false); + } + } + + setAllocationValues(allocationValuesReset); + }; + + const onAllocate = (isProceedingToAllocateWithLowUQScore?: boolean) => { + if (userNonce === undefined || projectsEpoch === undefined) { + return; + } + /** + * Whenever user wants to send an empty allocation (no projects, or all of them value 0) + * Push one element with value 0. It should be fixed on BE by creating "personal all" endpoint, + * but there is no ticket for it yet. + */ + const allocationValuesNew = [...allocationValues]; + if (allocationValuesNew.length === 0) { + allocationValuesNew.push({ + address: projectsEpoch.projectsAddresses[0], + value: '0', + }); + } + + if ( + !userAllocations?.hasUserAlreadyDoneAllocation && + uqScore === 20n && + !isProceedingToAllocateWithLowUQScore + ) { + setShowLowUQScoreModal(true); + return; + } + + if (isProceedingToAllocateWithLowUQScore) { + setShowLowUQScoreModal(false); + } + + // this condition must always be last due to ModalAllocationLowUqScore + // if uqScore == 20n, the signature request is triggered in ModalAllocationLowUqScore + if (isContract) { + setIsWaitingForFirstMultisigSignature(true); + toastService.showToast({ + message: t('multisigSignatureToast.message'), + name: 'allocationMultisigInitialSignature', + title: t('multisigSignatureToast.title'), + type: 'warning', + }); + } + allocateEvent.emit(allocationValuesNew, isManualMode); + }; + + useEffect(() => { + if (!userAllocations || isManualMode) { + return; + } + if (userAllocationsOriginal?.isManuallyEdited) { + setIsManualMode(true); + return; + } + setIsManualMode(false); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [userAllocations?.isManuallyEdited]); + + useEffect(() => { + if (!isRewardsForProjectsSet || isFetchingUserAllocation) { + return; + } + + if (userAllocations && userAllocations.elements.length > 0) { + setAllocationValues(userAllocations.elements); + setPercentageProportionsWrapper(userAllocations.elements, rewardsForProjects); + onResetAllocationValues({ allocationValuesNew: userAllocations.elements }); + return; + } + onResetAllocationValues(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ + currentEpoch, + allocations, + isRewardsForProjectsSet, + isFetchingUserAllocation, + userAllocations?.elements.length, + userNonce, + isRewardsForProjectsSet, + ]); + + useEffect(() => { + if ( + !currentEpoch || + !isDecisionWindowOpen || + !userAllocations || + currentEpoch < 2 || + !userAllocations.hasUserAlreadyDoneAllocation + ) { + return; + } + const userAllocationsAddresses = userAllocations.elements.map(({ address }) => address); + /** + * Whenever user did an allocation and removed/unhearted project they previously allocated to, + * land on edit. + * + * Otherwise, land on summary. + */ + if (allocations.length < userAllocationsAddresses.length) { + setCurrentView('edit'); + return; + } + + return () => { + if (!isDecisionWindowOpen || allocations.length < userAllocationsAddresses.length) { + return; + } + setCurrentView('summary'); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [currentEpoch, isDecisionWindowOpen, userAllocations?.elements.length]); + + useEffect(() => { + const areAllValuesZero = !allocationValues.some(element => element.value !== '0.0'); + if ( + allocationValues.length === 0 || + areAllValuesZero || + addressesWithError.length > 0 || + !isDecisionWindowOpen || + !isConnected + ) { + return; + } + mutateAsyncAllocateSimulateDebounced( + currentView === 'edit' ? allocationValues : userAllocations?.elements, + ); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ + currentView, + mutateAsyncAllocateSimulateDebounced, + addressesWithError, + allocationValues, + isDecisionWindowOpen, + userAllocations?.elements?.length, + userNonce, + ]); + + const onChangeAllocationItemValue = ( + newAllocationValue: AllocationValue, + isManualModeEnforced = false, + ) => { + const { allocationValuesArrayNew, rewardsForProjectsNew } = + getAllocationValuesAfterManualChange({ + allocationValues, + individualReward, + // When deleting by button isManualMode does not trigger manual mode. When typing, it does. + isManualMode: isManualModeEnforced ? true : isManualMode, + newAllocationValue: newAllocationValue || '0', + rewardsForProjects, + setAddressesWithError, + }); + + setAllocationValues(allocationValuesArrayNew); + setRewardsForProjects(rewardsForProjectsNew); + + if (isManualModeEnforced) { + setPercentageProportionsWrapper(allocationValuesArrayNew, rewardsForProjectsNew); + } + + if (isManualModeEnforced) { + setIsManualMode(true); + } + }; + + const onRemoveAllocationElement = (address: string) => { + onAddRemoveFromAllocate(address); + onChangeAllocationItemValue({ address, value: '0' }); + }; + + const isLoading = + allocationValues === undefined || + (isConnected && isFetchingUserNonce) || + (isConnected && isFetchingUserAllocation) || + (isFetchingUpcomingBudget && !isRefetchingUpcomingBudget); + + const areAllocationsAvailableOrAlreadyDone = + (allocationValues !== undefined && !isEmpty(allocations)) || + (!!userAllocations?.hasUserAlreadyDoneAllocation && userAllocations.elements.length > 0); + const hasUserIndividualReward = !!individualReward && individualReward !== 0n; + const areButtonsDisabled = + isLoading || + !isConnected || + !isDecisionWindowOpen || + (!areAllocationsAvailableOrAlreadyDone && rewardsForProjects !== 0n) || + !individualReward || + isWaitingForFirstMultisigSignature; + + const allocationsWithRewards = getAllocationsWithRewards({ + allocationValues, + areAllocationsAvailableOrAlreadyDone, + projectsIpfsWithRewards, + userAllocationsElements: userAllocations?.elements, + }); + + const isEpoch1 = currentEpoch === 1; + + const showAllocationBottomNavigation = + !isEpoch1 && + (areAllocationsAvailableOrAlreadyDone || rewardsForProjects === 0n) && + hasUserIndividualReward && + isDecisionWindowOpen; + + useEffect(() => { + if (!walletAddress || !isContract || isWaitingForFirstMultisigSignature) { + return; + } + const getPendingMultisigSignatures = () => { + apiGetPendingMultisigSignatures(walletAddress!, SignatureOpType.ALLOCATION).then(data => { + if (isWaitingForAllMultisigSignatures && !data.hash) { + onAllocateSuccess(); + } + setIsWaitingForAllMultisigSignatures(!!data.hash); + }); + }; + + if (!isWaitingForAllMultisigSignatures) { + getPendingMultisigSignatures(); + return; + } + + const intervalId = setInterval(getPendingMultisigSignatures, 2500); + + return () => { + clearInterval(intervalId); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ + walletAddress, + isWaitingForAllMultisigSignatures, + isContract, + isWaitingForFirstMultisigSignature, + ]); + + useEffect(() => { + navRef.current = document.getElementById(LAYOUT_NAVBAR_ID); + }, []); + + return ( +
+
{t('allocateRewards')}
+
+ {!isEpoch1 && ( + + )} + {!isEpoch1 && ( + 0} + isLocked={currentView === 'summary'} + isManuallyEdited={isManualMode} + setRewardsForProjectsCallback={onResetAllocationValues} + /> + )} + {currentView === 'edit' ? ( + areAllocationsAvailableOrAlreadyDone && ( + + {allocationsWithRewards.length > 0 + ? allocationsWithRewards!.map( + ({ + address, + isAllocatedTo, + isLoadingError, + value, + profileImageSmall, + name, + }) => ( + 0} + name={name} + onChange={onChangeAllocationItemValue} + onRemoveAllocationElement={() => onRemoveAllocationElement(address)} + profileImageSmall={profileImageSmall} + rewardsProps={{ + isLoadingAllocateSimulate, + simulatedMatched: allocationSimulated?.matched.find( + element => element.address === address, + )?.value, + }} + setAddressesWithError={setAddressesWithError} + value={value} + /> + ), + ) + : allocations.map(allocation => ( + + ))} + + ) + ) : ( + + )} + setShowLowUQScoreModal(false), + }} + onAllocate={() => onAllocate(true)} + /> + + {showAllocationBottomNavigation && + (isDesktop ? boxesWrapperRef.current : navRef.current) && + createPortal( + onResetAllocationValues({ shouldReset: true })} + />, + isDesktop ? boxesWrapperRef.current! : navRef.current!, + )} +
+
+ ); +}; + +export default Allocation; diff --git a/client/src/components/Allocation/AllocationItem/AllocationItem.module.scss b/client/src/components/Allocation/AllocationItem/AllocationItem.module.scss index fc7786db98..7e6969c191 100644 --- a/client/src/components/Allocation/AllocationItem/AllocationItem.module.scss +++ b/client/src/components/Allocation/AllocationItem/AllocationItem.module.scss @@ -28,7 +28,7 @@ $removeButtonHeightWidthDesktop: 10.4rem; background-color: $color-octant-grey1; } - @media #{$desktop-up} { + @media #{$tablet-up} { height: $removeButtonHeightWidthDesktop; width: $removeButtonHeightWidthDesktop; } @@ -65,9 +65,10 @@ $removeButtonHeightWidthDesktop: 10.4rem; border-radius: 50%; height: 4.8rem; width: 4.8rem; + display: none; - @media #{$tablet-down} { - display: none; + @media #{$tablet-up} { + display: initial; } } diff --git a/client/src/components/Allocation/AllocationItemRewards/AllocationItemRewards.tsx b/client/src/components/Allocation/AllocationItemRewards/AllocationItemRewards.tsx index 93bb16dc36..4e8718eb0b 100644 --- a/client/src/components/Allocation/AllocationItemRewards/AllocationItemRewards.tsx +++ b/client/src/components/Allocation/AllocationItemRewards/AllocationItemRewards.tsx @@ -4,13 +4,14 @@ import { useTranslation } from 'react-i18next'; import Svg from 'components/ui/Svg'; import useGetValuesToDisplay from 'hooks/helpers/useGetValuesToDisplay'; +import useMediaQuery from 'hooks/helpers/useMediaQuery'; import useProjectsDonors from 'hooks/queries/donors/useProjectsDonors'; import useCurrentEpoch from 'hooks/queries/useCurrentEpoch'; import useIsDecisionWindowOpen from 'hooks/queries/useIsDecisionWindowOpen'; import useMatchedProjectRewards from 'hooks/queries/useMatchedProjectRewards'; import useUqScore from 'hooks/queries/useUqScore'; import useUserAllocations from 'hooks/queries/useUserAllocations'; -import { person } from 'svg/misc'; +import { hammer, person } from 'svg/misc'; import bigintAbs from 'utils/bigIntAbs'; import getRewardsSumWithValueAndSimulation from 'utils/getRewardsSumWithValueAndSimulation'; import { parseUnitsBigInt } from 'utils/parseUnitsBigInt'; @@ -98,7 +99,7 @@ const AllocationItemRewards: FC = ({ value, }) => { const { t } = useTranslation('translation', { - keyPrefix: 'views.allocation.allocationItem', + keyPrefix: 'components.allocation.allocationItem', }); const [isSimulateVisible, setIsSimulateVisible] = useState(false); const { data: currentEpoch } = useCurrentEpoch(); @@ -106,6 +107,7 @@ const AllocationItemRewards: FC = ({ const { data: isDecisionWindowOpen } = useIsDecisionWindowOpen(); const { data: matchedProjectRewards } = useMatchedProjectRewards(); const { data: uqScore } = useUqScore(currentEpoch!); + const { isMobile } = useMediaQuery(); const { data: projectsDonors } = useProjectsDonors(); const projectDonors = projectsDonors?.[address]; @@ -137,9 +139,10 @@ const AllocationItemRewards: FC = ({ const getValuesToDisplay = useGetValuesToDisplay(); - const isNewSimulatedPositive = userAllocationToThisProject - ? parseUnitsBigInt(valueToUse) >= userAllocationToThisProject - : true; + const isNewSimulatedPositive = + isDecisionWindowOpen && userAllocationToThisProject + ? parseUnitsBigInt(valueToUse) >= userAllocationToThisProject + : true; const simulatedMatchedBigInt = simulatedMatched ? parseUnitsBigInt(simulatedMatched, 'wei') @@ -221,6 +224,27 @@ const AllocationItemRewards: FC = ({ userAllocationToThisProject={userAllocationToThisProject} valueToUse={valueToUse} /> + {!isMobile && !isLoadingAllocateSimulate && !isSimulateVisible && ( +
+ + {`${isNewSimulatedPositive ? '' : '-'}${yourImpactFormatted.primary}`} +
+ )}
); }; diff --git a/client/src/components/Allocation/AllocationLowUqScore/AllocationLowUqScore.tsx b/client/src/components/Allocation/AllocationLowUqScore/AllocationLowUqScore.tsx index a730348d73..9e91b6d674 100644 --- a/client/src/components/Allocation/AllocationLowUqScore/AllocationLowUqScore.tsx +++ b/client/src/components/Allocation/AllocationLowUqScore/AllocationLowUqScore.tsx @@ -11,14 +11,16 @@ import styles from './AllocationLowUqScore.module.scss'; import AllocationLowUqScoreProps from './types'; const AllocationLowUqScore: FC = ({ onAllocate }) => { - const { t } = useTranslation('translation', { keyPrefix: 'views.allocation.lowUQScoreModal' }); + const { t } = useTranslation('translation', { + keyPrefix: 'components.allocation.lowUQScoreModal', + }); const [isChecked, setIsChecked] = useState(false); const navigate = useNavigate(); return ( <>
- ]} i18nKey="views.allocation.lowUQScoreModal.text" /> + ]} i18nKey="components.allocation.lowUQScoreModal.text" />
= ({ isLeftButtonDisabled, areButtonsDisabled, - currentView, isLoading, onAllocate, onResetValues, - setCurrentView, isWaitingForAllMultisigSignatures, }) => { const { t } = useTranslation('translation', { keyPrefix: 'components.dedicated.allocationNavigation', }); + const { currentView, setCurrentView } = useAllocationsStore(state => ({ + currentView: state.data.currentView, + setCurrentView: state.setCurrentView, + })); + const commonProps = { isDisabled: areButtonsDisabled, }; diff --git a/client/src/components/Allocation/AllocationNavigation/types.ts b/client/src/components/Allocation/AllocationNavigation/types.ts index 3e48ea5a3a..a3caaa5724 100644 --- a/client/src/components/Allocation/AllocationNavigation/types.ts +++ b/client/src/components/Allocation/AllocationNavigation/types.ts @@ -1,12 +1,8 @@ -import { CurrentView } from 'views/AllocationView/types'; - export default interface AllocationNavigationProps { areButtonsDisabled: boolean; - currentView: CurrentView; isLeftButtonDisabled: boolean; isLoading: boolean; isWaitingForAllMultisigSignatures?: boolean; onAllocate: () => void; onResetValues: () => void; - setCurrentView: (newView: CurrentView) => void; } diff --git a/client/src/components/Allocation/AllocationRewardsBox/AllocationRewardsBox.module.scss b/client/src/components/Allocation/AllocationRewardsBox/AllocationRewardsBox.module.scss index ff39724944..f10b660003 100644 --- a/client/src/components/Allocation/AllocationRewardsBox/AllocationRewardsBox.module.scss +++ b/client/src/components/Allocation/AllocationRewardsBox/AllocationRewardsBox.module.scss @@ -1,106 +1,33 @@ .root { - position: relative; - color: $color-octant-grey5; - padding: 2rem 2.4rem 2.3rem; + min-height: 7.2rem; + max-height: 7.2rem; + border-radius: $border-radius-16; + background-color: $color-octant-grey8; + padding: 1.6rem 1.6rem 0; + font-weight: $font-weight-bold; + text-align: left; + margin-bottom: 1.6rem; - @media #{$tablet-down} { - padding: 1.6rem; - } -} - -.title { - font-size: $font-size-18; - margin: 0 0 0 0.8rem; - color: $color-octant-green; - line-height: 2.4rem; - - &.greyTitle { - color: $color-octant-grey5; + @media #{$tablet-up} { + padding: 2rem 2.4rem 0; } - @media #{$tablet-down} { + .value { + color: $color-octant-green; font-size: $font-size-16; - line-height: 2rem; - } -} - -.subtitle { - margin-left: 0.8rem; -} - -.isManualBadge { - position: absolute; - top: 2.4rem; - right: 2.4rem; - border: 0.1rem solid $color-octant-green; - padding: 0.2rem 0.5rem; - text-transform: uppercase; - border-radius: $border-radius-04; - color: $color-octant-green; - background: $color-octant-green5; - line-height: 1.4rem; -} - -.sliderWrapper { - display: flex; - align-items: center; - width: 100%; - height: 7.2rem; - background-color: $color-octant-grey6; - border-radius: $border-radius-08; - padding: 0 1.6rem; - margin: 1.6rem 0 1.2rem; - - &.isManuallyEdited { - background-color: $color-octant-green5; - } - - @media #{$tablet-down} { - margin: 1.1rem 0 0.8rem; - } -} - -.sections { - display: flex; - justify-content: space-between; - align-self: stretch; - margin: 0 0.8rem; -} - -.section { - &:not(.isDisabled):not(.isLocked) { - cursor: pointer; - } - - .header { - line-height: 1.6rem; - padding: 0.4rem 0 0.1rem 0; - } + line-height: 2.4rem; - .value { - line-height: 2rem; - font-size: $font-size-18; - color: $color-octant-dark; - height: 1.6rem; + @media #{$tablet-up} { + font-size: $font-size-18; + } &.isGrey { color: $color-octant-grey5; } - - @media #{$tablet-down} { - font-size: $font-size-16; - } - } - - &:first-child { - text-align: left; - } - - &:last-child { - text-align: right; } - &.isDisabled { + .label { + font-size: $font-size-12; color: $color-octant-grey5; } } diff --git a/client/src/components/Allocation/AllocationRewardsBox/AllocationRewardsBox.tsx b/client/src/components/Allocation/AllocationRewardsBox/AllocationRewardsBox.tsx index ca97574569..e93bf25d0b 100644 --- a/client/src/components/Allocation/AllocationRewardsBox/AllocationRewardsBox.tsx +++ b/client/src/components/Allocation/AllocationRewardsBox/AllocationRewardsBox.tsx @@ -1,106 +1,28 @@ import cx from 'classnames'; -import throttle from 'lodash/throttle'; -import React, { FC, useState, useCallback, useMemo } from 'react'; +import React, { FC, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; -import ModalAllocationValuesEdit from 'components/Allocation/ModalAllocationValuesEdit'; -import BoxRounded from 'components/ui/BoxRounded'; -import Slider from 'components/ui/Slider'; import useGetValuesToDisplay from 'hooks/helpers/useGetValuesToDisplay'; import useIndividualReward from 'hooks/queries/useIndividualReward'; import useIsDecisionWindowOpen from 'hooks/queries/useIsDecisionWindowOpen'; import useUpcomingBudget from 'hooks/queries/useUpcomingBudget'; -import useAllocationsStore from 'store/allocations/store'; import styles from './AllocationRewardsBox.module.scss'; import AllocationRewardsBoxProps from './types'; -const AllocationRewardsBox: FC = ({ - className, - isDisabled, - isManuallyEdited, - isLocked, - isError, - setRewardsForProjectsCallback, -}) => { +const AllocationRewardsBox: FC = ({ isDisabled, isLocked }) => { const { i18n, t } = useTranslation('translation', { keyPrefix: 'components.dedicated.allocationRewardsBox', }); const { data: individualReward } = useIndividualReward(); const { data: upcomingBudget } = useUpcomingBudget(); const { data: isDecisionWindowOpen } = useIsDecisionWindowOpen(); - const [modalMode, setModalMode] = useState<'closed' | 'donate' | 'withdraw'>('closed'); - const { rewardsForProjects, setRewardsForProjects } = useAllocationsStore(state => ({ - rewardsForProjects: state.data.rewardsForProjects, - setRewardsForProjects: state.setRewardsForProjects, - })); const getValuesToDisplay = useGetValuesToDisplay(); const hasUserIndividualReward = !!individualReward && individualReward !== 0n; const isDecisionWindowOpenAndHasIndividualReward = hasUserIndividualReward && isDecisionWindowOpen; - const onSetRewardsForProjects = (rewardsForProjectsNew: bigint) => { - if (!individualReward || isDisabled) { - return; - } - setRewardsForProjects(rewardsForProjectsNew); - setRewardsForProjectsCallback({ rewardsForProjectsNew }); - }; - - const onUpdateValueModal = (newValue: bigint) => { - const rewardsForProjectsNew = modalMode === 'donate' ? newValue : individualReward! - newValue; - onSetRewardsForProjects(rewardsForProjectsNew); - }; - - const onUpdateValueSlider = (index: number) => { - if (!individualReward || isDisabled) { - return; - } - const rewardsForProjectsNew = (individualReward * BigInt(index)) / BigInt(100); - onSetRewardsForProjects(rewardsForProjectsNew); - }; - - // eslint-disable-next-line react-hooks/exhaustive-deps - const onUpdateValueSliderThrottled = useCallback( - throttle(onUpdateValueSlider, 250, { trailing: true }), - [isDisabled, individualReward, setRewardsForProjectsCallback], - ); - - const rewardsForProjectsFinal = isDecisionWindowOpenAndHasIndividualReward - ? rewardsForProjects - : BigInt(0); - const rewardsForWithdraw = isDecisionWindowOpenAndHasIndividualReward - ? individualReward - rewardsForProjects - : BigInt(0); - - const percentRewardsForProjects = isDecisionWindowOpenAndHasIndividualReward - ? Number((rewardsForProjects * BigInt(100)) / individualReward) - : 50; - const percentWithdraw = isDecisionWindowOpenAndHasIndividualReward - ? Number((rewardsForWithdraw * BigInt(100)) / individualReward) - : 50; - const sections = [ - { - header: isLocked ? t('donated') : t('donate'), - value: getValuesToDisplay({ - cryptoCurrency: 'ethereum', - showCryptoSuffix: true, - showLessThanOneCentFiat: !isDisabled, - valueCrypto: rewardsForProjectsFinal, - }).primary, - }, - { - header: i18n.t('common.personal'), - value: getValuesToDisplay({ - cryptoCurrency: 'ethereum', - showCryptoSuffix: true, - showLessThanOneCentFiat: !isDisabled, - valueCrypto: rewardsForWithdraw, - }).primary, - }, - ]; - const subtitle = useMemo(() => { if (isDecisionWindowOpen === false && upcomingBudget) { return t('availableDuringAllocation'); @@ -132,86 +54,19 @@ const AllocationRewardsBox: FC = ({ }, [isDecisionWindowOpen, individualReward, upcomingBudget]); return ( - - {!isDisabled && isManuallyEdited &&
{t('manual')}
} -
- -
-
- {sections.map(({ header, value }, index) => ( -
- isLocked || isDisabled ? {} : setModalMode(index === 0 ? 'donate' : 'withdraw') - } - > -
- {header} -
-
- {value} -
-
- ))} +
+
+ { + getValuesToDisplay({ + cryptoCurrency: 'ethereum', + showCryptoSuffix: true, + showLessThanOneCentFiat: !isDisabled, + valueCrypto: budget, + }).primary + }
- setModalMode('closed'), - }} - onUpdateValue={newValue => onUpdateValueModal(newValue)} - valueCryptoSelected={modalMode === 'donate' ? rewardsForProjects : rewardsForWithdraw} - valueCryptoTotal={individualReward!} - /> - +
{subtitle}
+
); }; diff --git a/client/src/components/Allocation/AllocationRewardsBox/types.ts b/client/src/components/Allocation/AllocationRewardsBox/types.ts index c3f5c44759..f0089e9538 100644 --- a/client/src/components/Allocation/AllocationRewardsBox/types.ts +++ b/client/src/components/Allocation/AllocationRewardsBox/types.ts @@ -1,8 +1,4 @@ export default interface AllocationRewardsBoxProps { - className?: string; isDisabled?: boolean; - isError: boolean; isLocked?: boolean; - isManuallyEdited?: boolean; - setRewardsForProjectsCallback: ({ rewardsForProjectsNew }) => void; } diff --git a/client/src/components/Allocation/AllocationSliderBox/AllocationSliderBox.module.scss b/client/src/components/Allocation/AllocationSliderBox/AllocationSliderBox.module.scss new file mode 100644 index 0000000000..e32133d3d9 --- /dev/null +++ b/client/src/components/Allocation/AllocationSliderBox/AllocationSliderBox.module.scss @@ -0,0 +1,87 @@ +.root { + position: relative; + color: $color-octant-grey5; + padding: 1.6rem; + + @media #{$tablet-up} { + padding: 2.4rem; + } +} + +.isManualBadge { + position: absolute; + top: 2.4rem; + right: 2.4rem; + border: 0.1rem solid $color-octant-green; + padding: 0.2rem 0.5rem; + text-transform: uppercase; + border-radius: $border-radius-04; + color: $color-octant-green; + background: $color-octant-green5; + line-height: 1.4rem; +} + +.sliderWrapper { + display: flex; + align-items: center; + width: 100%; + background-color: $color-octant-grey6; + border-radius: $border-radius-08; + padding: 0 1.6rem; + margin-bottom: 0.8rem; + height: 7.2rem; + + &.isManuallyEdited { + background-color: $color-octant-green5; + } + + @media #{$tablet-up} { + margin-bottom: 1.6rem; + height: 10.4rem; + } +} + +.sections { + display: flex; + justify-content: space-between; + align-self: stretch; + margin: 0 0.8rem; +} + +.section { + &:not(.isDisabled):not(.isLocked) { + cursor: pointer; + } + + .header { + font-size: $font-size-12; + line-height: 2.4rem; + } + + .value { + line-height: 2rem; + font-size: $font-size-16; + color: $color-octant-dark; + height: 1.6rem; + + &.isGrey { + color: $color-octant-grey5; + } + + @media #{$tablet-up} { + font-size: $font-size-18; + } + } + + &:first-child { + text-align: left; + } + + &:last-child { + text-align: right; + } + + &.isDisabled { + color: $color-octant-grey5; + } +} diff --git a/client/src/components/Allocation/AllocationSliderBox/AllocationSliderBox.tsx b/client/src/components/Allocation/AllocationSliderBox/AllocationSliderBox.tsx new file mode 100644 index 0000000000..c2b5d5569a --- /dev/null +++ b/client/src/components/Allocation/AllocationSliderBox/AllocationSliderBox.tsx @@ -0,0 +1,175 @@ +import cx from 'classnames'; +import throttle from 'lodash/throttle'; +import React, { FC, useState, useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; + +import ModalAllocationValuesEdit from 'components/Allocation/ModalAllocationValuesEdit'; +import BoxRounded from 'components/ui/BoxRounded'; +import Slider from 'components/ui/Slider'; +import useGetValuesToDisplay from 'hooks/helpers/useGetValuesToDisplay'; +import useIndividualReward from 'hooks/queries/useIndividualReward'; +import useIsDecisionWindowOpen from 'hooks/queries/useIsDecisionWindowOpen'; +import useAllocationsStore from 'store/allocations/store'; + +import styles from './AllocationSliderBox.module.scss'; +import AllocationSliderBoxProps from './types'; + +const AllocationSliderBox: FC = ({ + className, + isDisabled, + isManuallyEdited, + isLocked, + isError, + setRewardsForProjectsCallback, +}) => { + const { i18n, t } = useTranslation('translation', { + keyPrefix: 'components.dedicated.allocationRewardsBox', + }); + const { data: individualReward } = useIndividualReward(); + const { data: isDecisionWindowOpen } = useIsDecisionWindowOpen(); + const [modalMode, setModalMode] = useState<'closed' | 'donate' | 'withdraw'>('closed'); + const { rewardsForProjects, setRewardsForProjects } = useAllocationsStore(state => ({ + rewardsForProjects: state.data.rewardsForProjects, + setRewardsForProjects: state.setRewardsForProjects, + })); + const getValuesToDisplay = useGetValuesToDisplay(); + + const hasUserIndividualReward = !!individualReward && individualReward !== 0n; + const isDecisionWindowOpenAndHasIndividualReward = + hasUserIndividualReward && isDecisionWindowOpen; + + const onSetRewardsForProjects = (rewardsForProjectsNew: bigint) => { + if (!individualReward || isDisabled) { + return; + } + setRewardsForProjects(rewardsForProjectsNew); + setRewardsForProjectsCallback({ rewardsForProjectsNew }); + }; + + const onUpdateValueModal = (newValue: bigint) => { + const rewardsForProjectsNew = modalMode === 'donate' ? newValue : individualReward! - newValue; + onSetRewardsForProjects(rewardsForProjectsNew); + }; + + const onUpdateValueSlider = (index: number) => { + if (!individualReward || isDisabled) { + return; + } + const rewardsForProjectsNew = (individualReward * BigInt(index)) / BigInt(100); + onSetRewardsForProjects(rewardsForProjectsNew); + }; + + // eslint-disable-next-line react-hooks/exhaustive-deps + const onUpdateValueSliderThrottled = useCallback( + throttle(onUpdateValueSlider, 250, { trailing: true }), + [isDisabled, individualReward, setRewardsForProjectsCallback], + ); + + const rewardsForProjectsFinal = isDecisionWindowOpenAndHasIndividualReward + ? rewardsForProjects + : BigInt(0); + const rewardsForWithdraw = isDecisionWindowOpenAndHasIndividualReward + ? individualReward - rewardsForProjects + : BigInt(0); + + const percentRewardsForProjects = isDecisionWindowOpenAndHasIndividualReward + ? Number((rewardsForProjects * BigInt(100)) / individualReward) + : 50; + const percentWithdraw = isDecisionWindowOpenAndHasIndividualReward + ? Number((rewardsForWithdraw * BigInt(100)) / individualReward) + : 50; + const sections = [ + { + header: isLocked ? t('donated') : t('donate'), + value: getValuesToDisplay({ + cryptoCurrency: 'ethereum', + showCryptoSuffix: true, + showLessThanOneCentFiat: !isDisabled, + valueCrypto: rewardsForProjectsFinal, + }).primary, + }, + { + header: i18n.t('common.personal'), + value: getValuesToDisplay({ + cryptoCurrency: 'ethereum', + showCryptoSuffix: true, + showLessThanOneCentFiat: !isDisabled, + valueCrypto: rewardsForWithdraw, + }).primary, + }, + ]; + + return ( + + {!isDisabled && isManuallyEdited &&
{t('manual')}
} +
+ +
+
+ {sections.map(({ header, value }, index) => ( +
+ isLocked || isDisabled ? {} : setModalMode(index === 0 ? 'donate' : 'withdraw') + } + > +
+ {header} +
+
+ {value} +
+
+ ))} +
+ setModalMode('closed'), + }} + onUpdateValue={newValue => onUpdateValueModal(newValue)} + valueCryptoSelected={modalMode === 'donate' ? rewardsForProjects : rewardsForWithdraw} + valueCryptoTotal={individualReward!} + /> +
+ ); +}; + +export default AllocationSliderBox; diff --git a/client/src/components/Allocation/AllocationSliderBox/index.tsx b/client/src/components/Allocation/AllocationSliderBox/index.tsx new file mode 100644 index 0000000000..1e04edb4f9 --- /dev/null +++ b/client/src/components/Allocation/AllocationSliderBox/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './AllocationSliderBox'; diff --git a/client/src/components/Allocation/AllocationSliderBox/types.ts b/client/src/components/Allocation/AllocationSliderBox/types.ts new file mode 100644 index 0000000000..7d976b8eb0 --- /dev/null +++ b/client/src/components/Allocation/AllocationSliderBox/types.ts @@ -0,0 +1,8 @@ +export default interface AllocationSliderBoxProps { + className?: string; + isDisabled?: boolean; + isError: boolean; + isLocked?: boolean; + isManuallyEdited?: boolean; + setRewardsForProjectsCallback: ({ rewardsForProjectsNew }) => void; +} diff --git a/client/src/components/Allocation/AllocationSummaryProject/AllocationSummaryProject.tsx b/client/src/components/Allocation/AllocationSummaryProject/AllocationSummaryProject.tsx index fec865d120..e69d565462 100644 --- a/client/src/components/Allocation/AllocationSummaryProject/AllocationSummaryProject.tsx +++ b/client/src/components/Allocation/AllocationSummaryProject/AllocationSummaryProject.tsx @@ -21,7 +21,7 @@ const AllocationSummaryProject: FC = ({ value, }) => { const { t } = useTranslation('translation', { - keyPrefix: 'views.allocation.allocationItem', + keyPrefix: 'components.allocation.allocationItem', }); const isDonationAboveThreshold = useIsDonationAboveThreshold({ projectAddress: address }); const { diff --git a/client/src/components/Allocation/AllocationTipTiles/AllocationTipTiles.tsx b/client/src/components/Allocation/AllocationTipTiles/AllocationTipTiles.tsx index 97e2fd2b3b..07ff1f506e 100644 --- a/client/src/components/Allocation/AllocationTipTiles/AllocationTipTiles.tsx +++ b/client/src/components/Allocation/AllocationTipTiles/AllocationTipTiles.tsx @@ -18,7 +18,7 @@ import styles from './AllocationTipTiles.module.scss'; import AllocationTipTilesProps from './types'; const AllocationTipTiles: FC = ({ className }) => { - const { t } = useTranslation('translation', { keyPrefix: 'views.allocation.tip' }); + const { t } = useTranslation('translation', { keyPrefix: 'components.allocation.tip' }); const navigate = useNavigate(); const { isDesktop } = useMediaQuery(); const { address, isConnected } = useAccount(); @@ -101,8 +101,8 @@ const AllocationTipTiles: FC = ({ className }) => { components={[]} i18nKey={ isDesktop - ? 'views.allocation.tip.uqTooLow.text.desktop' - : 'views.allocation.tip.uqTooLow.text.mobile' + ? 'components.allocation.tip.uqTooLow.text.desktop' + : 'components.allocation.tip.uqTooLow.text.mobile' } /> } diff --git a/client/src/components/Allocation/ModalAllocationLowUqScore/ModalAllocationLowUqScore.tsx b/client/src/components/Allocation/ModalAllocationLowUqScore/ModalAllocationLowUqScore.tsx index 01fa5a8e8d..b955b14f11 100644 --- a/client/src/components/Allocation/ModalAllocationLowUqScore/ModalAllocationLowUqScore.tsx +++ b/client/src/components/Allocation/ModalAllocationLowUqScore/ModalAllocationLowUqScore.tsx @@ -13,7 +13,9 @@ const ModalAllocationLowUqScore: FC = ({ modalProps, onAllocate, }) => { - const { t } = useTranslation('translation', { keyPrefix: 'views.allocation.lowUQScoreModal' }); + const { t } = useTranslation('translation', { + keyPrefix: 'components.allocation.lowUQScoreModal', + }); return ( & { + value: string; +}; + +export type PercentageProportions = { + [key: string]: number; +}; + +export type AllocationValue = { + address: string; + value: string; +}; + +export type AllocationValues = AllocationValue[]; + +export type AllocationWithPositiveValueBigInt = { + projectAddress: string; + value: bigint; +}; diff --git a/client/src/components/Allocation/utils.test.ts b/client/src/components/Allocation/utils.test.ts new file mode 100644 index 0000000000..906ee5ca70 --- /dev/null +++ b/client/src/components/Allocation/utils.test.ts @@ -0,0 +1,379 @@ +/* eslint-disable @typescript-eslint/naming-convention */ + +import { formatUnitsBigInt } from 'utils/formatUnitsBigInt'; +import { parseUnitsBigInt } from 'utils/parseUnitsBigInt'; + +import { + getAllocationValuesInitialState, + getAllocationValuesAfterManualChange, + getAllocationValuesWithRewardsSplitted, +} from './utils'; + +describe('getAllocationValuesWithRewardsSplitted', () => { + it('properly distributes the difference when sum is lower than restToDistribute', () => { + expect( + getAllocationValuesWithRewardsSplitted({ + allocationValues: [ + { address: '0xA', value: '0.2' }, + { address: '0xB', value: '0.3' }, + { address: '0xC', value: '0.5' }, + ], + restToDistribute: parseUnitsBigInt('2'), + }), + ).toEqual([ + { address: '0xA', value: '0.2' }, + { address: '0xB', value: '0.3' }, + { address: '0xC', value: '1.5' }, + ]); + }); + + it('properly distributes the difference when sum is lower than restToDistribute and last element cant fill the difference', () => { + expect( + getAllocationValuesWithRewardsSplitted({ + allocationValues: [ + { address: '0xA', value: '0.2' }, + { address: '0xB', value: '0.3' }, + { address: '0xC', value: '0' }, + ], + restToDistribute: parseUnitsBigInt('2'), + }), + ).toEqual([ + { address: '0xA', value: '1.7' }, + { address: '0xB', value: '0.3' }, + { address: '0xC', value: '0' }, + ]); + }); + + it('properly distributes the difference when sum is bigger than restToDistribute and last element can fill the difference', () => { + expect( + getAllocationValuesWithRewardsSplitted({ + allocationValues: [ + { address: '0xA', value: '1.0' }, + { address: '0xB', value: '2.0' }, + { address: '0xC', value: '4.5' }, + ], + restToDistribute: parseUnitsBigInt('5'), + }), + ).toEqual([ + { address: '0xA', value: '1.0' }, + { address: '0xB', value: '2.0' }, + { address: '0xC', value: '2' }, + ]); + }); + + it('properly distributes the difference when sum is bigger than restToDistribute and last element cant fill the difference', () => { + expect( + getAllocationValuesWithRewardsSplitted({ + allocationValues: [ + { address: '0xA', value: '1.0' }, + { address: '0xB', value: '4.5' }, + { address: '0xC', value: '0.5' }, + ], + restToDistribute: parseUnitsBigInt('5'), + }), + ).toEqual([ + { address: '0xA', value: '1.0' }, + { address: '0xB', value: '3.5' }, + { address: '0xC', value: '0.5' }, + ]); + }); + + it('returns empty array when given empty array', () => { + expect( + getAllocationValuesWithRewardsSplitted({ + allocationValues: [], + restToDistribute: BigInt(500), + }), + ).toEqual([]); + }); + + it('returns initial values when restToDistribute is zero', () => { + expect( + getAllocationValuesWithRewardsSplitted({ + allocationValues: [ + { address: '0xA', value: formatUnitsBigInt(BigInt(100)) }, + { address: '0xB', value: formatUnitsBigInt(BigInt(200)) }, + { address: '0xC', value: formatUnitsBigInt(BigInt(450)) }, + ], + restToDistribute: BigInt(0), + }), + ).toEqual([ + { address: '0xA', value: formatUnitsBigInt(BigInt(100)) }, + { address: '0xB', value: formatUnitsBigInt(BigInt(200)) }, + { address: '0xC', value: formatUnitsBigInt(BigInt(450)) }, + ]); + }); +}); + +describe('getAllocationValuesInitialState', () => { + const propsCommon = { + allocationValues: [ + { address: '0xA', value: '0.2' }, + { address: '0xB', value: '0.3' }, + { address: '0xC', value: '0.5' }, + ], + allocations: ['0xA', '0xB', '0xC'], + isManualMode: false, + percentageProportions: {}, + rewardsForProjects: parseUnitsBigInt('1'), + shouldReset: false, + userAllocationsElements: [], + }; + + describe('Case A (shouldReset, userAllocations provided)', () => { + it('when allocations match userAllocationsElements', () => { + expect( + getAllocationValuesInitialState({ + ...propsCommon, + allocations: ['0xA', '0xB', '0xC'], + isManualMode: true, + rewardsForProjects: parseUnitsBigInt('0.6'), + shouldReset: true, + userAllocationsElements: [ + { address: '0xA', value: '0.3' }, + { address: '0xB', value: '0.2' }, + { address: '0xC', value: '0.1' }, + ], + }), + ).toEqual([ + { address: '0xA', value: '0.3' }, + { address: '0xB', value: '0.2' }, + { address: '0xC', value: '0.1' }, + ]); + }); + + it('when allocations do not match userAllocationsElements', () => { + expect( + getAllocationValuesInitialState({ + ...propsCommon, + allocations: ['0xA', '0xB', '0xC', '0xD'], + isManualMode: true, + rewardsForProjects: parseUnitsBigInt('0.6'), + shouldReset: true, + userAllocationsElements: [ + { address: '0xA', value: '0.3' }, + { address: '0xB', value: '0.2' }, + { address: '0xC', value: '0.1' }, + ], + }), + ).toEqual([ + { address: '0xA', value: '0.3' }, + { address: '0xB', value: '0.2' }, + { address: '0xC', value: '0.1' }, + { address: '0xD', value: '0' }, + ]); + }); + }); + + describe('Case B (shouldReset, userAllocations not provided)', () => { + it('default', () => { + expect( + getAllocationValuesInitialState({ + ...propsCommon, + allocationValues: [ + { address: '0xA', value: '0.3' }, + { address: '0xB', value: '0.2' }, + { address: '0xC', value: '0.1' }, + ], + isManualMode: false, + rewardsForProjects: parseUnitsBigInt('1'), + userAllocationsElements: [], + }), + ).toEqual([ + { address: '0xA', value: '0.333333333333333333' }, + { address: '0xB', value: '0.333333333333333333' }, + { address: '0xC', value: '0.333333333333333334' }, + ]); + }); + }); + + describe('Case C (!isManualMode) ', () => { + it('when !isManualMode', () => { + expect( + getAllocationValuesInitialState({ + ...propsCommon, + isManualMode: false, + userAllocationsElements: [], + }), + ).toEqual([ + { address: '0xA', value: '0.333333333333333333' }, + { address: '0xB', value: '0.333333333333333333' }, + { address: '0xC', value: '0.333333333333333334' }, + ]); + }); + }); + + describe('Case D (all the rest)', () => { + it('when isManualMode, userAllocationsElements & allocationValues', () => { + expect( + getAllocationValuesInitialState({ + ...propsCommon, + allocationValues: [ + { address: '0xA', value: '0.2' }, + { address: '0xB', value: '0.3' }, + { address: '0xC', value: '0.5' }, + ], + isManualMode: true, + userAllocationsElements: [ + { address: '0xA', value: '0.5' }, + { address: '0xB', value: '0.3' }, + { address: '0xC', value: '0.2' }, + ], + }), + ).toEqual([ + { address: '0xA', value: '0.2' }, + { address: '0xB', value: '0.3' }, + { address: '0xC', value: '0.5' }, + ]); + }); + + it('when isManualMode, userAllocationsElements & !allocationValues', () => { + expect( + getAllocationValuesInitialState({ + ...propsCommon, + allocationValues: [], + isManualMode: true, + userAllocationsElements: [ + { address: '0xA', value: '0.5' }, + { address: '0xB', value: '0.3' }, + { address: '0xC', value: '0.2' }, + ], + }), + ).toEqual([ + { address: '0xA', value: '0.5' }, + { address: '0xB', value: '0.3' }, + { address: '0xC', value: '0.2' }, + ]); + }); + + it('when isManualMode, userAllocationsElements, allocationValues & percentageProportions', () => { + expect( + getAllocationValuesInitialState({ + ...propsCommon, + allocationValues: [ + { address: '0xA', value: '0.2' }, + { address: '0xB', value: '0.3' }, + { address: '0xC', value: '0.5' }, + ], + isManualMode: true, + percentageProportions: { + '0xA': 60, + '0xB': 35, + '0xC': 5, + }, + userAllocationsElements: [ + { address: '0xA', value: '0.5' }, + { address: '0xB', value: '0.3' }, + { address: '0xC', value: '0.2' }, + ], + }), + ).toEqual([ + { address: '0xA', value: '0.6' }, + { address: '0xB', value: '0.35' }, + { address: '0xC', value: '0.05' }, + ]); + }); + }); +}); + +describe('getAllocationValuesAfterManualChange', () => { + const propsCommon = { + allocationValues: [ + { address: '0xA', value: '0.333333333333333333' }, + { address: '0xB', value: '0.333333333333333333' }, + { address: '0xC', value: '0.333333333333333334' }, + ], + allocations: ['0xA', '0xB', '0xC'], + individualReward: parseUnitsBigInt('2'), + isManualMode: false, + newAllocationValue: { + address: '0xA', + value: '0.05', + }, + rewardsForProjects: parseUnitsBigInt('1'), + setAddressesWithError: () => {}, + }; + + it('!individualReward', () => { + expect( + getAllocationValuesAfterManualChange({ ...propsCommon, individualReward: undefined }), + ).toEqual({ + allocationValuesArrayNew: [ + { address: '0xA', value: '0.333333333333333333' }, + { address: '0xB', value: '0.333333333333333333' }, + { address: '0xC', value: '0.333333333333333334' }, + ], + rewardsForProjectsNew: parseUnitsBigInt('1'), + }); + }); + + it('allocationValuesArrayNewSum>(individualReward)', () => { + expect( + getAllocationValuesAfterManualChange({ + ...propsCommon, + newAllocationValue: { + address: '0xA', + value: '100', + }, + }), + ).toEqual({ + allocationValuesArrayNew: [ + { address: '0xA', value: '0' }, + { address: '0xB', value: '0.333333333333333333' }, + { address: '0xC', value: '0.333333333333333334' }, + ], + rewardsForProjectsNew: parseUnitsBigInt('1'), + }); + }); + + it('correctly updates allocationValues when isManualMode', () => { + expect( + getAllocationValuesAfterManualChange({ + ...propsCommon, + isManualMode: true, + }), + ).toEqual({ + allocationValuesArrayNew: [ + { address: '0xA', value: '0.05' }, + { address: '0xB', value: '0.333333333333333333' }, + { address: '0xC', value: '0.333333333333333334' }, + ], + rewardsForProjectsNew: parseUnitsBigInt('0.716666666666666667'), + }); + }); + + it('correctly updates allocationValues when !isManualMode', () => { + expect(getAllocationValuesAfterManualChange(propsCommon)).toEqual({ + allocationValuesArrayNew: [ + { address: '0xA', value: '0.05' }, + { address: '0xB', value: '0.333333333333333333' }, + { address: '0xC', value: '0.616666666666666667' }, + ], + rewardsForProjectsNew: parseUnitsBigInt('1'), + }); + }); + + it('correctly updates allocationValues when !isManualMode, rewardsForProjectsNew is zero when all values are 0', () => { + expect( + getAllocationValuesAfterManualChange({ + ...propsCommon, + allocationValues: [ + { address: '0xA', value: '0.333333333333333333' }, + { address: '0xB', value: '0' }, + { address: '0xC', value: '0' }, + ], + newAllocationValue: { + address: '0xA', + value: '0', + }, + }), + ).toEqual({ + allocationValuesArrayNew: [ + { address: '0xA', value: '0' }, + { address: '0xB', value: '0' }, + { address: '0xC', value: '0' }, + ], + rewardsForProjectsNew: parseUnitsBigInt('0'), + }); + }); +}); diff --git a/client/src/components/Allocation/utils.ts b/client/src/components/Allocation/utils.ts new file mode 100644 index 0000000000..aaab36aabd --- /dev/null +++ b/client/src/components/Allocation/utils.ts @@ -0,0 +1,292 @@ +import React from 'react'; + +import { AllocationItemWithAllocations } from 'components/Allocation/AllocationItem/types'; +import { ProjectIpfsWithRewards } from 'hooks/queries/useProjectsIpfsWithRewards'; +import { formatUnitsBigInt } from 'utils/formatUnitsBigInt'; +import getSortedElementsByTotalValueOfAllocationsAndAlphabetical from 'utils/getSortedElementsByTotalValueOfAllocationsAndAlphabetical'; +import { parseUnitsBigInt } from 'utils/parseUnitsBigInt'; + +import { + AllocationValue, + AllocationValues, + PercentageProportions, + UserAllocationElementString, +} from './types'; + +export function getAllocationValuesWithRewardsSplitted({ + allocationValues, + restToDistribute, +}: { + allocationValues: AllocationValues; + restToDistribute: bigint; +}): AllocationValues { + if (allocationValues.length === 0) { + return []; + } + const allocationValuesNew = [...allocationValues]; + const allocationValuesSum = allocationValuesNew.reduce((acc, { value }) => { + return acc + parseUnitsBigInt(value); + }, BigInt(0)); + + if (restToDistribute === 0n) { + return allocationValues; + } + + /** + * Since percentage calculated in getAllocationValuesInitialState is not perfect, + * chances are allocationValuesSum is lower than restToDistribute. + */ + if (allocationValuesSum < restToDistribute) { + const difference = restToDistribute - allocationValuesSum; + const lastElementValue = parseUnitsBigInt( + allocationValuesNew[allocationValuesNew.length - 1].value, + ); + + // We don't want to add value to element user chose to set as 0. + if (lastElementValue > 0n) { + allocationValuesNew[allocationValuesNew.length - 1].value = formatUnitsBigInt( + lastElementValue + difference, + ); + } else { + // Find first non-zero element. + const elementIndexToChange = allocationValuesNew.findIndex( + element => parseUnitsBigInt(element.value) > 0n, + ); + allocationValuesNew[elementIndexToChange].value = formatUnitsBigInt( + parseUnitsBigInt(allocationValuesNew[elementIndexToChange].value) + difference, + ); + } + } + /** + * Since percentage calculated in getAllocationValuesInitialState is not perfect, + * chances are allocationValuesSum is bigger than restToDistribute. + */ + if (allocationValuesSum > restToDistribute) { + const difference = allocationValuesSum - restToDistribute; + const lastElementValue = parseUnitsBigInt( + allocationValuesNew[allocationValuesNew.length - 1].value, + ); + + if (lastElementValue >= difference) { + allocationValuesNew[allocationValuesNew.length - 1].value = formatUnitsBigInt( + lastElementValue - difference, + ); + } else { + const elementIndexToChange = allocationValuesNew.findIndex( + element => parseUnitsBigInt(element.value) > difference, + ); + allocationValuesNew[elementIndexToChange].value = formatUnitsBigInt( + parseUnitsBigInt(allocationValuesNew[elementIndexToChange].value) - difference, + ); + } + } + + return allocationValuesNew; +} + +export function getAllocationValuesInitialState({ + allocationValues, + allocations, + isManualMode, + percentageProportions, + rewardsForProjects, + shouldReset, + userAllocationsElements, +}: { + allocationValues: AllocationValues; + allocations: string[]; + isManualMode: boolean; + percentageProportions: PercentageProportions; + rewardsForProjects: bigint; + shouldReset: boolean; + userAllocationsElements: UserAllocationElementString[]; +}): AllocationValues { + if (shouldReset) { + const allocationValuesNew = allocations.map(allocation => { + // Case A (see utils.test.ts). + if (userAllocationsElements.length > 0) { + const userAllocationValue = userAllocationsElements.find( + element => element.address === allocation, + )?.value; + const userAllocationValueFinal = userAllocationValue || '0'; + return { + address: allocation, + // @ts-expect-error TS method collision. + value: userAllocationValueFinal.toLocaleString('fullWide', { useGrouping: false }), + }; + } + // Case B (see utils.test.ts). + return { + address: allocation, + // @ts-expect-error TS method collision. + value: '0'.toLocaleString('fullWide', { useGrouping: false }), + }; + }); + + return getAllocationValuesWithRewardsSplitted({ + allocationValues: allocationValuesNew, + restToDistribute: BigInt(0), + }); + } + if (!isManualMode) { + // Case C (see utils.test.ts). + const allocationValuesNew = allocations.map(allocation => ({ + address: allocation, + value: formatUnitsBigInt(rewardsForProjects / BigInt(allocations.length)).toLocaleString( + // @ts-expect-error TS method collision. + 'fullWide', + { + useGrouping: false, + }, + ), + })); + + return getAllocationValuesWithRewardsSplitted({ + allocationValues: allocationValuesNew, + restToDistribute: rewardsForProjects, + }); + } + // Case D (see utils.test.ts). + + const allocationValuesNew = allocations.map(allocation => { + const percentageProportion = percentageProportions[allocation]; + const allocationValue = allocationValues.find(element => element.address === allocation)?.value; + const userAllocationValue = userAllocationsElements.find( + element => element.address === allocation, + )?.value; + const userValue = allocationValue || userAllocationValue || '0'; + // Value for the project set as valueUser multiplied by percentage. + const value = ( + percentageProportion === undefined + ? userValue + : formatUnitsBigInt((rewardsForProjects * BigInt(percentageProportion)) / 100n) + ) + // @ts-expect-error TS method collision. + .toLocaleString('fullWide', { useGrouping: false }); + return { + address: allocation, + value, + }; + }); + + return getAllocationValuesWithRewardsSplitted({ + allocationValues: allocationValuesNew, + restToDistribute: rewardsForProjects, + }); +} + +export function getAllocationsWithRewards({ + projectsIpfsWithRewards, + allocationValues, + areAllocationsAvailableOrAlreadyDone, + userAllocationsElements, +}: { + allocationValues: AllocationValues | undefined; + areAllocationsAvailableOrAlreadyDone: boolean; + projectsIpfsWithRewards: ProjectIpfsWithRewards[]; + userAllocationsElements: UserAllocationElementString[] | undefined; +}): AllocationItemWithAllocations[] { + const isDataDefined = + projectsIpfsWithRewards && + projectsIpfsWithRewards.length > 0 && + areAllocationsAvailableOrAlreadyDone; + let allocationsWithRewards = isDataDefined + ? allocationValues!.map(allocationValue => { + const projectIpfsWithRewards = projectsIpfsWithRewards.find( + ({ address }) => address === allocationValue.address, + )!; + const isAllocatedTo = !!userAllocationsElements?.find( + ({ address }) => address === allocationValue.address, + ); + + return { + isAllocatedTo, + ...allocationValue, + ...projectIpfsWithRewards, + }; + }) + : []; + + allocationsWithRewards.sort(({ value: valueA }, { value: valueB }) => { + const valueABigInt = parseUnitsBigInt(valueA || '0'); + const valueBBigInt = parseUnitsBigInt(valueB || '0'); + if (valueABigInt < valueBBigInt) { + return 1; + } + if (valueABigInt > valueBBigInt) { + return -1; + } + return 0; + }); + + allocationsWithRewards = getSortedElementsByTotalValueOfAllocationsAndAlphabetical( + allocationsWithRewards as AllocationItemWithAllocations[], + ) as AllocationItemWithAllocations[]; + + return allocationsWithRewards; +} + +export function getAllocationValuesAfterManualChange({ + newAllocationValue, + allocationValues, + rewardsForProjects, + individualReward, + isManualMode, + setAddressesWithError, +}: { + allocationValues: AllocationValues; + individualReward: bigint | undefined; + isManualMode: boolean; + newAllocationValue: AllocationValue; + rewardsForProjects: bigint; + setAddressesWithError: React.Dispatch>; +}): { allocationValuesArrayNew: AllocationValues; rewardsForProjectsNew: bigint } { + if (!individualReward) { + return { + allocationValuesArrayNew: allocationValues, + rewardsForProjectsNew: rewardsForProjects, + }; + } + + const allocationValuesArrayNew = allocationValues.map(element => ({ + ...element, + value: + element.address === newAllocationValue.address ? newAllocationValue.value : element.value, + })); + + const allocationValuesArrayNewSum = allocationValuesArrayNew.reduce( + (acc, { value }) => acc + parseUnitsBigInt(value || '0'), + BigInt(0), + ); + + if (allocationValuesArrayNewSum > individualReward) { + setAddressesWithError(prev => [...prev, newAllocationValue.address]); + return { + allocationValuesArrayNew: allocationValues.map(element => ({ + ...element, + value: element.address === newAllocationValue.address ? '0' : element.value, + })), + rewardsForProjectsNew: rewardsForProjects, + }; + } + + if (isManualMode) { + return { + allocationValuesArrayNew, + rewardsForProjectsNew: allocationValuesArrayNewSum, + }; + } + + const rewardsForProjectsNew = allocationValuesArrayNewSum === 0n ? BigInt(0) : rewardsForProjects; + + return { + allocationValuesArrayNew: + allocationValuesArrayNew.length === 1 + ? allocationValuesArrayNew + : getAllocationValuesWithRewardsSplitted({ + allocationValues: allocationValuesArrayNew, + restToDistribute: rewardsForProjectsNew, + }), + rewardsForProjectsNew, + }; +} diff --git a/client/src/components/Home/HomeGridDonations/HomeGridDonations.tsx b/client/src/components/Home/HomeGridDonations/HomeGridDonations.tsx index 8df2846ed4..e06f9dfb30 100644 --- a/client/src/components/Home/HomeGridDonations/HomeGridDonations.tsx +++ b/client/src/components/Home/HomeGridDonations/HomeGridDonations.tsx @@ -11,6 +11,8 @@ import useUserAllocationsAllEpochs from 'hooks/helpers/useUserAllocationsAllEpoc import useCurrentEpoch from 'hooks/queries/useCurrentEpoch'; import useIsDecisionWindowOpen from 'hooks/queries/useIsDecisionWindowOpen'; import useUserAllocations from 'hooks/queries/useUserAllocations'; +import useAllocationsStore from 'store/allocations/store'; +import useLayoutStore from 'store/layout/store'; import styles from './HomeGridDonations.module.scss'; import HomeGridDonationsProps from './types'; @@ -27,6 +29,13 @@ const HomeGridDonations: FC = ({ className }) => { const { data: userAllocations, isFetching: isFetchingUserAllocations } = useUserAllocations(); const { data: currentEpoch } = useCurrentEpoch(); const { data: isDecisionWindowOpen } = useIsDecisionWindowOpen(); + const { setShowAllocationDrawer } = useLayoutStore(state => ({ + setShowAllocationDrawer: state.setShowAllocationDrawer, + })); + + const { setCurrentView } = useAllocationsStore(state => ({ + setCurrentView: state.setCurrentView, + })); const areAllocationsEmpty = !isConnected || @@ -49,7 +58,14 @@ const HomeGridDonations: FC = ({ className }) => { titleSuffix={ isDecisionWindowOpen ? ( // TODO: open allocation drawer in edit mode -> https://linear.app/golemfoundation/issue/OCT-1907/allocate-drawer - ) : null diff --git a/client/src/components/shared/Layout/LayoutNavbar/LayoutNavbar.module.scss b/client/src/components/shared/Layout/LayoutNavbar/LayoutNavbar.module.scss index 925a8a7e09..bbd81d94d9 100644 --- a/client/src/components/shared/Layout/LayoutNavbar/LayoutNavbar.module.scss +++ b/client/src/components/shared/Layout/LayoutNavbar/LayoutNavbar.module.scss @@ -14,6 +14,8 @@ } .navigation { + display: flex; + flex-direction: column; border-radius: $border-radius-32; background: $color-white; box-shadow: $box-shadow-1; @@ -25,16 +27,8 @@ width: 43.2rem; } - .navigationBottomSuffix { - padding: 1.6rem; - border-bottom: 0.1rem solid $color-octant-grey3; - - @media #{$tablet-up} { - padding: 2.4rem; - } - } - .buttons { + order: 1; display: flex; justify-content: space-between; align-items: center; diff --git a/client/src/components/shared/Layout/LayoutNavbar/LayoutNavbar.tsx b/client/src/components/shared/Layout/LayoutNavbar/LayoutNavbar.tsx index 543cfe4f07..e8bddda460 100644 --- a/client/src/components/shared/Layout/LayoutNavbar/LayoutNavbar.tsx +++ b/client/src/components/shared/Layout/LayoutNavbar/LayoutNavbar.tsx @@ -6,6 +6,7 @@ import { useLocation } from 'react-router-dom'; import Button from 'components/ui/Button'; import Svg from 'components/ui/Svg'; import { ELEMENT_POSITION_FIXED_CLASSNAME } from 'constants/css'; +import { LAYOUT_NAVBAR_ID } from 'constants/domElementsIds'; import { WINDOW_PROJECTS_SCROLL_Y } from 'constants/window'; import useIsProjectAdminMode from 'hooks/helpers/useIsProjectAdminMode'; import useMediaQuery from 'hooks/helpers/useMediaQuery'; @@ -46,10 +47,7 @@ const LayoutNavbar: FC = ({ navigationBottomSuffix }) => { className={cx(styles.navigationWrapper, ELEMENT_POSITION_FIXED_CLASSNAME)} data-test="Navbar" > -
{ const getValuesToDisplay = useGetValuesToDisplay(); const isProjectAdminMode = useIsProjectAdminMode(); const { data: isPatronMode } = useIsPatronMode(); + const { data: currentEpoch } = useCurrentEpoch(); + const { data: isDecisionWindowOpen } = useIsDecisionWindowOpen(); + const { isMobile } = useMediaQuery(); + + const { data: matchedProjectRewards, isFetching: isFetchingMatchedProjectRewards } = + useMatchedProjectRewards(isDecisionWindowOpen ? undefined : currentEpoch! - 1, { + enabled: isProjectAdminMode || isPatronMode, + }); + + const projectMatchedProjectRewards = isProjectAdminMode + ? matchedProjectRewards?.find( + ({ address: matchedProjectRewardsAddress }) => address === matchedProjectRewardsAddress, + ) + : undefined; + + const totalMatechProjectsRewards = + isProjectAdminMode || isPatronMode + ? matchedProjectRewards?.reduce((acc, { matched }) => acc + matched, 0n) + : undefined; // We count only rewards from epochs user did an action -- allocation or was a patron. const totalRewards = individualRewardAllEpochs.reduce((acc, curr, currentIndex) => { @@ -50,28 +73,86 @@ const HomeRewards = (): ReactNode => { valueCrypto: totalRewards, }).primary; + const currentDonationsToDisplay = getValuesToDisplay({ + cryptoCurrency: 'ethereum', + showCryptoSuffix: true, + valueCrypto: projectMatchedProjectRewards?.allocated, + }).primary; + + const currentMatchFundingToDisplay = getValuesToDisplay({ + cryptoCurrency: 'ethereum', + showCryptoSuffix: true, + valueCrypto: projectMatchedProjectRewards?.matched, + }).primary; + + const epochTotalMatchFundingToDisplay = getValuesToDisplay({ + cryptoCurrency: 'ethereum', + showCryptoSuffix: true, + valueCrypto: totalMatechProjectsRewards, + }).primary; + + const currentRewardsLabel = useMemo(() => { + if (isProjectAdminMode) { + if (isMobile) { + return t('donations'); + } + return t('currentDonations'); + } + return t('currentRewards'); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isProjectAdminMode, isMobile]); + + const totalRewardsLabel = useMemo(() => { + if (isProjectAdminMode) { + if (isMobile) { + return t('matchFunding'); + } + return t('currentMatchFunding'); + } + return t('totalRewards'); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isProjectAdminMode, isMobile]); + + const rewardsRateLabel = useMemo(() => { + if (isProjectAdminMode || isPatronMode) { + if (isMobile) { + return t('epochMF'); + } + return t('epochTotalMatchFunding'); + } + return t('rewardsRate'); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isProjectAdminMode, isMobile, isPatronMode]); + const tiles = [ { - isLoadingValue: isFetchingIndividualReward, + isLoadingValue: isProjectAdminMode + ? isFetchingMatchedProjectRewards + : isFetchingIndividualReward, key: 'currentRewards', - label: isProjectAdminMode ? t('currentDonations') : t('currentRewards'), - value: currentRewardsToDisplay, + label: currentRewardsLabel, + value: isProjectAdminMode ? currentDonationsToDisplay : currentRewardsToDisplay, }, { - isLoadingValue: isConnected - ? isFetchingIndividualRewardAllEpochs || - isFetchingUserAllAllocations || - isFetchingEpochPatronsAllEpochs - : false, + isLoadingValue: + isConnected && + (isProjectAdminMode + ? isFetchingMatchedProjectRewards + : isFetchingIndividualRewardAllEpochs || + isFetchingUserAllAllocations || + isFetchingEpochPatronsAllEpochs), key: 'totalRewards', - label: isProjectAdminMode ? t('currentMatchFunding') : t('totalRewards'), - value: totalRewardsToDisplay, + label: totalRewardsLabel, + value: isProjectAdminMode ? currentMatchFundingToDisplay : totalRewardsToDisplay, }, { + // TODO: https://linear.app/golemfoundation/issue/OCT-1870/home-rewards-rate + isLoadingValue: isProjectAdminMode || isPatronMode ? isFetchingMatchedProjectRewards : false, + key: 'rewardsRate', - label: isProjectAdminMode || isPatronMode ? t('epochTotalMatchFunding') : t('rewardsRate'), + label: rewardsRateLabel, // TODO: https://linear.app/golemfoundation/issue/OCT-1870/home-rewards-rate - value: null, + value: isProjectAdminMode || isPatronMode ? epochTotalMatchFundingToDisplay : null, }, ]; diff --git a/client/src/components/Projects/ProjectsList/ProjectsList.tsx b/client/src/components/Projects/ProjectsList/ProjectsList.tsx index a53514ef96..fdceb083f7 100644 --- a/client/src/components/Projects/ProjectsList/ProjectsList.tsx +++ b/client/src/components/Projects/ProjectsList/ProjectsList.tsx @@ -22,10 +22,8 @@ const ProjectsList: FC = ({ }); const { data: projectsEpoch, isFetching: isFetchingProjectsEpoch } = useProjectsEpoch(epoch); - const { - data: projectsIpfsWithRewards, - isFetching: isFetchingProjectsWithRewards, - } = useProjectsIpfsWithRewards(epoch); + const { data: projectsIpfsWithRewards, isFetching: isFetchingProjectsWithRewards } = + useProjectsIpfsWithRewards(epoch); const epochDurationLabel = useEpochDurationLabel(epoch); const isLoading = isFetchingProjectsEpoch || isFetchingProjectsWithRewards; @@ -60,22 +58,22 @@ const ProjectsList: FC = ({ {projectsIpfsWithRewards.length > 0 && !isFetchingProjectsWithRewards ? projectsIpfsWithRewards.map((projectIpfsWithRewards, index) => ( - - )) + + )) : projectsEpoch?.projectsAddresses?.map((_, index) => ( - // eslint-disable-next-line react/no-array-index-key - - ))} + // eslint-disable-next-line react/no-array-index-key + + ))} ); diff --git a/client/src/hooks/queries/useMatchedProjectRewards.ts b/client/src/hooks/queries/useMatchedProjectRewards.ts index 2dec368e67..17c09a401a 100644 --- a/client/src/hooks/queries/useMatchedProjectRewards.ts +++ b/client/src/hooks/queries/useMatchedProjectRewards.ts @@ -48,7 +48,7 @@ function parseResponse(response: Response): ProjectRewards[] { export default function useMatchedProjectRewards( epoch?: number, - options?: UseQueryOptions, + options?: Omit, 'queryKey'>, ): UseQueryResult { const queryClient = useQueryClient(); const { data: currentEpoch } = useCurrentEpoch(); diff --git a/client/src/locales/en/translation.json b/client/src/locales/en/translation.json index 65998fe57d..587ca8a7cb 100644 --- a/client/src/locales/en/translation.json +++ b/client/src/locales/en/translation.json @@ -107,10 +107,13 @@ "homeRewards": { "currentRewards": "Current rewards", "currentDonations": "Current donations", + "donations": "Donations", "totalRewards": "Total rewards", "currentMatchFunding": "Current match funding", + "matchFunding": "Match funding", "rewardsRate": "Rewards rate", - "epochTotalMatchFunding": "Epoch total match funding" + "epochTotalMatchFunding": "Epoch total match funding", + "epochMF": "Epoch MF" }, "homeGridCurrentGlmLock": { "currentGlmLock": "Current GLM lock", From 200f6a9161aa73ea213a69fb36012a3bb66fd490 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Miko=C5=82ajczyk?= Date: Tue, 17 Sep 2024 21:26:10 +0200 Subject: [PATCH 094/241] patron mode modal portal --- .../ModalSettingsPatronMode/ModalSettingsPatronMode.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/client/src/components/Settings/ModalSettingsPatronMode/ModalSettingsPatronMode.tsx b/client/src/components/Settings/ModalSettingsPatronMode/ModalSettingsPatronMode.tsx index 79ee861d5d..9178c4db56 100644 --- a/client/src/components/Settings/ModalSettingsPatronMode/ModalSettingsPatronMode.tsx +++ b/client/src/components/Settings/ModalSettingsPatronMode/ModalSettingsPatronMode.tsx @@ -1,4 +1,5 @@ import React, { FC, useMemo } from 'react'; +import { createPortal } from 'react-dom'; import { useTranslation } from 'react-i18next'; import SettingsPatronMode from 'components/Settings/SettingsPatronMode'; @@ -15,7 +16,7 @@ const ModalSettingsPatronMode: FC = ({ modalProps // eslint-disable-next-line react-hooks/exhaustive-deps const isPatronModeEnabled = useMemo(() => isPatronMode, [modalProps.isOpen]); - return ( + return createPortal( = ({ modalProps className={styles.root} > - + , + document.body, ); }; From 785368a6f148457cac963033d9e8b8d8f22fa0ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Garbaci=C5=84ski?= <57113816+kgarbacinski@users.noreply.github.com> Date: Wed, 18 Sep 2024 12:04:20 +0300 Subject: [PATCH 095/241] OCT-1938: Propose a changelog (#414) ## Description ## Definition of Done 1. [ ] Acceptance criteria are met. 2. [ ] PR is manually tested before the merge by developer(s). - [ ] Happy path is manually checked. 3. [ ] PR is manually tested by QA when their assistance is required (1). - [ ] Octant Areas & Test Cases are checked for impact and updated if required (2). 4. [ ] Unit tests are added unless there is a reason to omit them. 5. [ ] Automated tests are added when required. 6. [ ] The code is merged. 7. [ ] Tech documentation is added / updated, reviewed and approved (including mandatory approval by a code owner, should such exist for changed files). - [ ] BE: Swagger documentation is updated. 8. [ ] When required by QA: - [ ] Deployed to the relevant environment. - [ ] Passed system tests. --- (1) Developer(s) in coordination with QA decide whether it's required. For small tickets introducing small changes QA assistance is most probably not required. (2) [Octant Areas & Test Cases](https://docs.google.com/spreadsheets/d/1cRe6dxuKJV3a4ZskAwWEPvrFkQm6rEfyUCYwLTYw_Cc). --- .github/pull_request_template.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 7f11afb385..c601038141 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -2,17 +2,18 @@ ## Definition of Done -1. [ ] Acceptance criteria are met. -2. [ ] PR is manually tested before the merge by developer(s). +1. [ ] If required, the desciption of your change is added to the [QA changelog](https://www.notion.so/octantapp/Changelog-for-the-QA-d96fa3b411cf488bb1d8d9a598d88281) +2. [ ] Acceptance criteria are met. +3. [ ] PR is manually tested before the merge by developer(s). - [ ] Happy path is manually checked. -3. [ ] PR is manually tested by QA when their assistance is required (1). +4. [ ] PR is manually tested by QA when their assistance is required (1). - [ ] Octant Areas & Test Cases are checked for impact and updated if required (2). -4. [ ] Unit tests are added unless there is a reason to omit them. -5. [ ] Automated tests are added when required. -6. [ ] The code is merged. -7. [ ] Tech documentation is added / updated, reviewed and approved (including mandatory approval by a code owner, should such exist for changed files). +5. [ ] Unit tests are added unless there is a reason to omit them. +6. [ ] Automated tests are added when required. +7. [ ] The code is merged. +8. [ ] Tech documentation is added / updated, reviewed and approved (including mandatory approval by a code owner, should such exist for changed files). - [ ] BE: Swagger documentation is updated. -8. [ ] When required by QA: +9. [ ] When required by QA: - [ ] Deployed to the relevant environment. - [ ] Passed system tests. From 16ac5ae23cc1a7bcb3e7f19617e8a6233ec899b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Garbaci=C5=84ski?= <57113816+kgarbacinski@users.noreply.github.com> Date: Wed, 18 Sep 2024 17:51:51 +0300 Subject: [PATCH 096/241] OCT-1843: Refetch when getting 404 error (#411) ## Description ## Definition of Done 1. [ ] Acceptance criteria are met. 2. [ ] PR is manually tested before the merge by developer(s). - [ ] Happy path is manually checked. 3. [ ] PR is manually tested by QA when their assistance is required (1). - [ ] Octant Areas & Test Cases are checked for impact and updated if required (2). 4. [ ] Unit tests are added unless there is a reason to omit them. 5. [ ] Automated tests are added when required. 6. [ ] The code is merged. 7. [ ] Tech documentation is added / updated, reviewed and approved (including mandatory approval by a code owner, should such exist for changed files). - [ ] BE: Swagger documentation is updated. 8. [ ] When required by QA: - [ ] Deployed to the relevant environment. - [ ] Passed system tests. --- (1) Developer(s) in coordination with QA decide whether it's required. For small tickets introducing small changes QA assistance is most probably not required. (2) [Octant Areas & Test Cases](https://docs.google.com/spreadsheets/d/1cRe6dxuKJV3a4ZskAwWEPvrFkQm6rEfyUCYwLTYw_Cc). --- backend/app/infrastructure/external_api/common.py | 3 ++- backend/app/modules/multisig_signatures/service/offchain.py | 3 ++- backend/app/modules/user/antisybil/service/initial.py | 4 +++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/backend/app/infrastructure/external_api/common.py b/backend/app/infrastructure/external_api/common.py index 462fd66e48..eaa545c7d7 100644 --- a/backend/app/infrastructure/external_api/common.py +++ b/backend/app/infrastructure/external_api/common.py @@ -1,4 +1,5 @@ import time +from http import HTTPStatus from typing import Callable, Dict from app.exceptions import ExternalApiException @@ -6,7 +7,7 @@ def retry_request( req_func: Callable, - status_code: int, + status_code: HTTPStatus, no_retries: int = 3, sleep_time: int = 1, **kwargs, diff --git a/backend/app/modules/multisig_signatures/service/offchain.py b/backend/app/modules/multisig_signatures/service/offchain.py index 89bfbc1b31..f0716f7b07 100644 --- a/backend/app/modules/multisig_signatures/service/offchain.py +++ b/backend/app/modules/multisig_signatures/service/offchain.py @@ -1,3 +1,4 @@ +from http import HTTPStatus from typing import List, Dict from app.context.manager import Context @@ -101,7 +102,7 @@ def save_pending_signature( def _verify_owner(self, user_address: str, message_hash: str): message_details = retry_request( req_func=get_message_details, - status_code=404, + status_code=HTTPStatus.NOT_FOUND, message_hash=message_hash, is_mainnet=self.is_mainnet, ) diff --git a/backend/app/modules/user/antisybil/service/initial.py b/backend/app/modules/user/antisybil/service/initial.py index a9ed1f5a5a..3314acd723 100644 --- a/backend/app/modules/user/antisybil/service/initial.py +++ b/backend/app/modules/user/antisybil/service/initial.py @@ -1,3 +1,5 @@ +from http import HTTPStatus + from flask import current_app as app from eth_utils.address import to_checksum_address @@ -52,7 +54,7 @@ def _retry_fetch(): raise ExternalApiException("GP: scoring is not completed yet", 503) if score["status"] != "DONE": - score = retry_request(_retry_fetch, 200) + score = retry_request(_retry_fetch, HTTPStatus.OK) all_stamps = fetch_stamps(user_address)["items"] cutoff = datetime.now() From af50d9167f984d97cc10b8e03793e9c097694ad3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Miko=C5=82ajczyk?= Date: Wed, 18 Sep 2024 18:18:15 +0200 Subject: [PATCH 097/241] oct-1882: metrics funding leaderboard --- .../Allocation/AllocationItem/types.ts | 2 +- .../HomeGridDonations/HomeGridDonations.tsx | 2 +- .../HomeGridDonations}/utils.ts | 0 .../MetricsEpochGridTopProjects.module.scss | 27 ++- .../MetricsEpochGridTopProjects.tsx | 27 ++- .../MetricsGrid/MetricsGrid.module.scss | 10 +- .../MetricsGridTile.module.scss | 12 +- .../MetricsNavigation.module.scss | 145 ------------ .../MetricsNavigation/MetricsNavigation.tsx | 212 ------------------ .../Metrics/MetricsNavigation/index.tsx | 2 - .../Metrics/MetricsNavigation/types.ts | 1 - .../MetricsPersonal/MetricsPersonal.tsx | 5 - ...MetricsPersonalGridAllocations.module.scss | 50 ----- .../MetricsPersonalGridAllocations.tsx | 63 ------ .../MetricsPersonalGridAllocations/index.tsx | 2 - .../MetricsPersonalGridAllocations/types.ts | 6 - .../utils.test.ts | 90 -------- .../MetricsProjectsList.module.scss | 4 +- .../MetricsProjectsList.tsx | 29 ++- .../Metrics/MetricsProjectsList/types.ts | 4 +- .../MetricsProjectsListItem.module.scss | 32 ++- .../MetricsProjectsListItem.tsx | 41 +++- .../Metrics/MetricsProjectsListItem/types.ts | 5 +- ...etricsProjectsListSkeletonItem.module.scss | 41 ++-- .../MetricsProjectsListSkeletonItem.tsx | 16 +- .../queries/useProjectsIpfsWithRewards.ts | 19 +- client/src/locales/en/translation.json | 5 + client/src/mocks/subgraph/projects.ts | 2 + ...lValueOfAllocationsAndAlphabetical.test.ts | 9 +- client/src/views/MetricsView/MetricsView.tsx | 2 - 30 files changed, 213 insertions(+), 652 deletions(-) rename client/src/components/{Metrics/MetricsPersonal/MetricsPersonalGridAllocations => Home/HomeGridDonations}/utils.ts (100%) delete mode 100644 client/src/components/Metrics/MetricsNavigation/MetricsNavigation.module.scss delete mode 100644 client/src/components/Metrics/MetricsNavigation/MetricsNavigation.tsx delete mode 100644 client/src/components/Metrics/MetricsNavigation/index.tsx delete mode 100644 client/src/components/Metrics/MetricsNavigation/types.ts delete mode 100644 client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridAllocations/MetricsPersonalGridAllocations.module.scss delete mode 100644 client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridAllocations/MetricsPersonalGridAllocations.tsx delete mode 100644 client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridAllocations/index.tsx delete mode 100644 client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridAllocations/types.ts delete mode 100644 client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridAllocations/utils.test.ts diff --git a/client/src/components/Allocation/AllocationItem/types.ts b/client/src/components/Allocation/AllocationItem/types.ts index 76396b4252..11119a94e8 100644 --- a/client/src/components/Allocation/AllocationItem/types.ts +++ b/client/src/components/Allocation/AllocationItem/types.ts @@ -12,7 +12,7 @@ export interface AllocationItemWithAllocations extends ProjectIpfsWithRewards { export default interface AllocationItemProps extends Omit< AllocationItemWithAllocations, - 'totalValueOfAllocations' | 'percentage' | 'numberOfDonors' + 'totalValueOfAllocations' | 'percentage' | 'numberOfDonors' | 'matchedRewards' | 'donations' > { className?: string; isError: boolean; diff --git a/client/src/components/Home/HomeGridDonations/HomeGridDonations.tsx b/client/src/components/Home/HomeGridDonations/HomeGridDonations.tsx index 3c03907674..d75dd1bfe8 100644 --- a/client/src/components/Home/HomeGridDonations/HomeGridDonations.tsx +++ b/client/src/components/Home/HomeGridDonations/HomeGridDonations.tsx @@ -3,7 +3,6 @@ import { Trans, useTranslation } from 'react-i18next'; import { useAccount } from 'wagmi'; import DonationsList from 'components/Home/HomeGridDonations/DonationsList'; -import { getReducedUserAllocationsAllEpochs } from 'components/Metrics/MetricsPersonal/MetricsPersonalGridAllocations/utils'; import GridTile from 'components/shared/Grid/GridTile'; import Button from 'components/ui/Button'; import Img from 'components/ui/Img'; @@ -16,6 +15,7 @@ import useLayoutStore from 'store/layout/store'; import styles from './HomeGridDonations.module.scss'; import HomeGridDonationsProps from './types'; +import { getReducedUserAllocationsAllEpochs } from './utils'; const HomeGridDonations: FC = ({ className }) => { const { t } = useTranslation('translation', { diff --git a/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridAllocations/utils.ts b/client/src/components/Home/HomeGridDonations/utils.ts similarity index 100% rename from client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridAllocations/utils.ts rename to client/src/components/Home/HomeGridDonations/utils.ts diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTopProjects/MetricsEpochGridTopProjects.module.scss b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTopProjects/MetricsEpochGridTopProjects.module.scss index fa09389653..14aa077378 100644 --- a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTopProjects/MetricsEpochGridTopProjects.module.scss +++ b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTopProjects/MetricsEpochGridTopProjects.module.scss @@ -1,12 +1,17 @@ .root { - max-height: 62.4rem; grid-row: span 2; grid-column: span 2; - height: 33.6rem; + height: 32.8rem; - @media #{$desktop-up} { - height: 62.4rem; + @media #{$tablet-up} { + height: 67.2rem; grid-row: span 4; + grid-column: span 2; + } + + @media #{$large-desktop-up} { + grid-row: span 4; + grid-column: span 4; } } @@ -39,3 +44,17 @@ height: 100%; } } + +.headers { + color: $color-octant-grey5; + font-weight: $font-weight-bold; + display: grid; + grid-template-columns: 8rem 11.8rem 11.8rem 11.8rem; + margin-left: 10rem; + font-size: $font-size-12; + text-align: left; + + .label { + margin-left: 0.2rem; + } +} diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTopProjects/MetricsEpochGridTopProjects.tsx b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTopProjects/MetricsEpochGridTopProjects.tsx index 14d1e4eb6b..6a38d496ef 100644 --- a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTopProjects/MetricsEpochGridTopProjects.tsx +++ b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTopProjects/MetricsEpochGridTopProjects.tsx @@ -18,22 +18,17 @@ const MetricsEpochGridTopProjects: FC = ({ }) => { const { t } = useTranslation('translation', { keyPrefix: 'views.metrics' }); const { epoch, lastEpoch } = useMetricsEpoch(); - const { isDesktop } = useMediaQuery(); + const { isLargeDesktop } = useMediaQuery(); const { data: isDecisionWindowOpen } = useIsDecisionWindowOpen(); const { data: projectsIpfsWithRewards } = useProjectsIpfsWithRewards( isDecisionWindowOpen && epoch === lastEpoch ? undefined : epoch, ); - const numberOfProjects = isDesktop ? 10 : 5; - const projects = - projectsIpfsWithRewards - .slice(0, numberOfProjects) - .map(({ totalValueOfAllocations, ...rest }) => ({ - epoch, - value: totalValueOfAllocations!, - ...rest, - })) || []; + projectsIpfsWithRewards.map(props => ({ + epoch, + ...props, + })) || []; return ( = ({ ), - title: t('topProjectsByEthRaised', { numberOfProjects }), + title: t('fundingLeaderboard'), + titleSuffix: isLargeDesktop ? ( +
+
{t('donors')}
+
{t('donations')}
+
{t('matchFunding')}
+
{t('total')}
+
+ ) : null, }, ]} size="custom" diff --git a/client/src/components/Metrics/MetricsGrid/MetricsGrid.module.scss b/client/src/components/Metrics/MetricsGrid/MetricsGrid.module.scss index 2246197510..db7f69583c 100644 --- a/client/src/components/Metrics/MetricsGrid/MetricsGrid.module.scss +++ b/client/src/components/Metrics/MetricsGrid/MetricsGrid.module.scss @@ -6,7 +6,15 @@ $gridGap: 1.6rem; gap: $gridGap; width: 100%; + @media #{$tablet-up} { + grid-template-columns: repeat(4, calc(calc(100% / 4) - calc(3 * $gridGap / 4))); + } + @media #{$desktop-up} { - grid-template-columns: 16.3rem 16.3rem 16.3rem 16.3rem; + grid-template-columns: repeat(6, calc(calc(100% / 6) - calc(5 * $gridGap / 6))); + } + + @media #{$large-desktop-up} { + grid-template-columns: repeat(8, 16.3rem); } } diff --git a/client/src/components/Metrics/MetricsGrid/MetricsGridTile/MetricsGridTile.module.scss b/client/src/components/Metrics/MetricsGrid/MetricsGridTile/MetricsGridTile.module.scss index 7975c6847a..2769c27b83 100644 --- a/client/src/components/Metrics/MetricsGrid/MetricsGridTile/MetricsGridTile.module.scss +++ b/client/src/components/Metrics/MetricsGrid/MetricsGridTile/MetricsGridTile.module.scss @@ -46,25 +46,23 @@ } .groupTitleWrapper { - padding: 1.2rem 1.6rem 0 2.4rem; + padding: 1.6rem; display: flex; align-items: center; margin: 0; - &.hasTitileBottomPadding { - padding-bottom: 1.2rem; - } - .title { text-align: left; font-size: $font-size-10; color: $color-octant-green; - text-transform: uppercase; font-weight: $font-weight-bold; letter-spacing: 0.03rem; - height: 3.2rem; + height: 2.4rem; align-items: center; display: flex; + border-radius: $border-radius-08; + background: $color-octant-grey6; + padding: 0 0.6rem; } } } diff --git a/client/src/components/Metrics/MetricsNavigation/MetricsNavigation.module.scss b/client/src/components/Metrics/MetricsNavigation/MetricsNavigation.module.scss deleted file mode 100644 index 203c19555f..0000000000 --- a/client/src/components/Metrics/MetricsNavigation/MetricsNavigation.module.scss +++ /dev/null @@ -1,145 +0,0 @@ -.root { - height: 6.4rem; - width: 100%; - border-radius: $border-radius-16; - background-color: $color-octant-grey8; - padding: 0.8rem 0.4rem; - display: flex; - position: sticky; - top: 1.6rem; - margin-bottom: 1.6rem; - z-index: $z-index-5; - - .box { - width: 100%; - display: flex; - } - - .circleSvg { - position: absolute; - } - - .circle { - transition: all $transition-time-1; - stroke-width: 0.4rem; - fill: transparent; - stroke: $color-octant-grey2; - stroke-linejoin: 'round'; - } - - .smallDotsWrapper { - $smallDotsWrapperTop: 2.6rem; - - position: absolute; - display: flex; - flex-direction: column; - justify-content: space-between; - height: 7.6rem; - top: $smallDotsWrapperTop; - - &.transformDots { - top: calc(50% + $smallDotsWrapperTop); - transform: translate(0, calc(-50% + $smallDotsWrapperTop)); - } - } - - .smallDot { - width: 0.4rem; - height: 0.4rem; - border-radius: 100%; - background-color: $color-octant-grey1; - - &.isActive { - background-color: $color-octant-green; - } - } - - @media #{$large-desktop-up} { - padding: 0; - margin: 0; - position: fixed; - height: 23.2rem; - width: 8.8rem; - right: calc(50% + 48rem); - top: 13.4rem; - background-color: transparent; - - .box { - flex-direction: column; - height: 100%; - width: 2.4rem; - background-color: $color-white; - border-radius: $border-radius-16; - - .sectionStep { - margin: 0; - - &:first-child { - .label, - .circleSvg { - top: 0.4rem; - } - } - - &:last-child { - .label, - .circleSvg { - bottom: 0.4rem; - } - - .smallDotsWrapper { - top: auto; - bottom: 2.6rem; - } - } - - .label { - transition: all $transition-time-1; - position: absolute; - left: calc(100% + 0.8rem); - &:hover { - color: $color-octant-grey11; - } - } - - &.isActive { - .label { - color: $color-octant-green; - } - .circle { - stroke: $color-octant-green; - } - } - } - } - } - - .sectionStep { - position: relative; - background-color: transparent; - color: $color-octant-grey5; - font-size: $font-size-14; - font-weight: $font-weight-bold; - display: flex; - align-items: center; - justify-content: center; - flex: 1; - margin: 0 0.4rem; - cursor: pointer; - transition: all 0.3s; - - &.isActive { - color: $color-octant-dark; - } - } - - .isActiveOverlay { - position: absolute; - background-color: $color-white; - color: $color-octant-dark; - width: 100%; - height: 100%; - border-radius: $border-radius-12; - z-index: -1; - } -} diff --git a/client/src/components/Metrics/MetricsNavigation/MetricsNavigation.tsx b/client/src/components/Metrics/MetricsNavigation/MetricsNavigation.tsx deleted file mode 100644 index f0fe2432fe..0000000000 --- a/client/src/components/Metrics/MetricsNavigation/MetricsNavigation.tsx +++ /dev/null @@ -1,212 +0,0 @@ -import cx from 'classnames'; -import { motion } from 'framer-motion'; -import { throttle } from 'lodash'; -import React, { ReactElement, useCallback, useEffect, useRef, useState } from 'react'; -import { useTranslation } from 'react-i18next'; - -import { - LAYOUT_BODY_ID, - METRICS_EPOCH_ID, - METRICS_GENERAL_ID, - METRICS_PERSONAL_ID, -} from 'constants/domElementsIds'; -import useMediaQuery from 'hooks/helpers/useMediaQuery'; - -import styles from './MetricsNavigation.module.scss'; -import { ActiveSection } from './types'; - -const MetricsNavigation = (): ReactElement => { - const ref = useRef(null); - const { i18n, t } = useTranslation('translation', { keyPrefix: 'views.metrics' }); - const { isLargeDesktop } = useMediaQuery(); - const forcedSectionRef = useRef(null); - const [activeSection, setActiveSection] = useState('epoch'); - const [activeDot, setActiveDot] = useState(1); - const throttleCallback = useCallback(throttle, []); - const numberOfDots = 8; - - const steps = [ - { - label: t('epoch'), - section: 'epoch', - sectionId: METRICS_EPOCH_ID, - }, - { - label: t('overview'), - section: 'general', - sectionId: METRICS_GENERAL_ID, - }, - { - label: i18n.t('common.personal'), - section: 'personal', - sectionId: METRICS_PERSONAL_ID, - }, - ]; - - const scrollToSection = (sectionId: string, section: ActiveSection) => { - if (!ref.current) { - return; - } - - const { height, marginBottom } = getComputedStyle(ref.current); - - const navigationBoxHeight = parseInt(height, 10); - const navigationBoxMarginBottom = parseInt(marginBottom, 10); - const sectionDividerHeight = 48; - - const valueToAddToOffstetTop = - (section !== 'epoch' ? sectionDividerHeight : 0) - - (isLargeDesktop ? 0 : navigationBoxHeight + navigationBoxMarginBottom); - - const element = document.getElementById(sectionId)!; - - const scrollToTop = element!.offsetTop + valueToAddToOffstetTop; - setActiveSection(section); - setActiveDot(1); - const maxScroll = element!.parentElement!.scrollHeight - window.innerHeight; - if ( - scrollToTop === window.scrollY || - (scrollToTop > maxScroll && window.scrollY === maxScroll) - ) { - return; - } - - forcedSectionRef.current = section; - window.scrollTo({ - behavior: 'smooth', - top: scrollToTop, - }); - }; - - useEffect(() => { - const metricsEpochTarget = document.getElementById(METRICS_EPOCH_ID)!; - const metricsGeneralTarget = document.getElementById(METRICS_GENERAL_ID)!; - const layoutBodyTarget = document.getElementById(LAYOUT_BODY_ID)!; - - const layoutComputedStyle = getComputedStyle(layoutBodyTarget); - - const layoutBodyHeight = parseInt(layoutComputedStyle.height, 10); - const layoutBodyPaddingTop = parseInt(layoutComputedStyle.paddingTop, 10); - const layoutBodyPaddingBottom = parseInt(layoutComputedStyle.paddingBottom, 10); - - const layoutBodyWithoutVerticalPadding = - layoutBodyHeight - layoutBodyPaddingTop - layoutBodyPaddingBottom; - - const scrollHeightWithoutPaddingTop = - layoutBodyHeight - window.innerHeight - layoutBodyPaddingTop; - - const percentageScrollShareEpochSection = - metricsEpochTarget.clientHeight / layoutBodyWithoutVerticalPadding; - const percentageScrollShareGeneralSection = - metricsGeneralTarget.clientHeight / layoutBodyWithoutVerticalPadding; - - const scrollShareEpochSection = - percentageScrollShareEpochSection * scrollHeightWithoutPaddingTop; - const scrollShareGeneralSection = - percentageScrollShareGeneralSection * scrollHeightWithoutPaddingTop; - - const stepEpochSection = - (percentageScrollShareEpochSection * scrollHeightWithoutPaddingTop) / 8; - const stepGeneralSection = - (percentageScrollShareGeneralSection * scrollHeightWithoutPaddingTop) / 8; - - const scrollEndListener = () => { - if (!forcedSectionRef.current) { - return; - } - - setTimeout(() => { - forcedSectionRef.current = null; - }, 100); - }; - - const scrollListener = () => { - if (forcedSectionRef.current) { - return; - } - - const scrollYWithoutPaddingTop = window.scrollY - layoutBodyPaddingTop; - - if (scrollYWithoutPaddingTop <= scrollShareEpochSection) { - setActiveSection('epoch'); - setActiveDot( - scrollYWithoutPaddingTop < 0 ? 1 : Math.ceil(scrollYWithoutPaddingTop / stepEpochSection), - ); - return; - } - - if ( - scrollYWithoutPaddingTop > scrollShareEpochSection && - scrollYWithoutPaddingTop <= scrollShareEpochSection + scrollShareGeneralSection - ) { - setActiveSection('general'); - setActiveDot( - Math.ceil((scrollYWithoutPaddingTop - scrollShareEpochSection) / stepGeneralSection), - ); - return; - } - - setActiveSection('personal'); - setActiveDot(1); - }; - - const throttledScrollListener = throttleCallback(scrollListener, 100); - - document.addEventListener('scroll', throttledScrollListener); - document.addEventListener('scrollend', scrollEndListener); - - return () => { - document.removeEventListener('scroll', throttledScrollListener); - document.removeEventListener('scrollend', scrollEndListener); - }; - }, [throttleCallback]); - - return ( -
-
- {steps.map(({ section, sectionId, label }) => ( -
scrollToSection(sectionId, section as ActiveSection)} - > - {isLargeDesktop && ( - <> - - - - {sectionId !== METRICS_PERSONAL_ID && ( -
- {[...Array(numberOfDots - 1).keys()].map(i => ( -
- ))} -
- )} - - )} - {label} - {!isLargeDesktop && activeSection === section ? ( - - ) : null} -
- ))} -
-
- ); -}; - -export default MetricsNavigation; diff --git a/client/src/components/Metrics/MetricsNavigation/index.tsx b/client/src/components/Metrics/MetricsNavigation/index.tsx deleted file mode 100644 index 03b889b968..0000000000 --- a/client/src/components/Metrics/MetricsNavigation/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line no-restricted-exports -export { default } from './MetricsNavigation'; diff --git a/client/src/components/Metrics/MetricsNavigation/types.ts b/client/src/components/Metrics/MetricsNavigation/types.ts deleted file mode 100644 index 96cc7892c7..0000000000 --- a/client/src/components/Metrics/MetricsNavigation/types.ts +++ /dev/null @@ -1 +0,0 @@ -export type ActiveSection = 'epoch' | 'general' | 'personal'; diff --git a/client/src/components/Metrics/MetricsPersonal/MetricsPersonal.tsx b/client/src/components/Metrics/MetricsPersonal/MetricsPersonal.tsx index e4729c1be4..2b11eb0a89 100644 --- a/client/src/components/Metrics/MetricsPersonal/MetricsPersonal.tsx +++ b/client/src/components/Metrics/MetricsPersonal/MetricsPersonal.tsx @@ -13,7 +13,6 @@ import useCryptoValues from 'hooks/queries/useCryptoValues'; import useSettingsStore from 'store/settings/store'; import styles from './MetricsPersonal.module.scss'; -import MetricsPersonalGridAllocations from './MetricsPersonalGridAllocations'; import MetricsPersonalGridDonationsProgressBar from './MetricsPersonalGridDonationsProgressBar'; import MetricsPersonalGridPatronDonations from './MetricsPersonalGridPatronDonations'; import MetricsPersonalGridTotalRewardsWithdrawals from './MetricsPersonalGridTotalRewardsWithdrawals'; @@ -50,10 +49,6 @@ const MetricsPersonal = (): ReactElement => { {isConnected ? ( - {wasUserEverAPatron && ( diff --git a/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridAllocations/MetricsPersonalGridAllocations.module.scss b/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridAllocations/MetricsPersonalGridAllocations.module.scss deleted file mode 100644 index c5a1c2ea28..0000000000 --- a/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridAllocations/MetricsPersonalGridAllocations.module.scss +++ /dev/null @@ -1,50 +0,0 @@ -.customSize { - min-height: 46.4rem; - grid-column: span 2; - grid-row: span 3; - max-height: 50.4rem; -} - -.numberOfAllocationsSuffix { - display: flex; - align-items: center; - justify-content: center; - height: 2.4rem; - width: 3.2rem; - font-size: $font-size-10; - font-weight: $font-weight-bold; - color: $color-octant-dark; - border-radius: $border-radius-08; - background-color: $color-octant-grey3; - margin-left: auto; -} - -.noAllocationsYet { - height: 100%; - width: 100%; - - .noAllocationsYetImage { - height: 8rem; - width: auto; - margin-top: 5.6rem; - } - - .noAllocationsYetLabel { - margin-top: 2.8rem; - font-size: $font-size-16; - font-weight: $font-weight-semibold; - color: $color-octant-grey5; - } -} - -.skeletonWrapper { - width: 100%; - height: 100%; - padding: 1rem 2.4rem; - - .skeleton { - @include skeleton(); - width: 100%; - height: 100%; - } -} diff --git a/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridAllocations/MetricsPersonalGridAllocations.tsx b/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridAllocations/MetricsPersonalGridAllocations.tsx deleted file mode 100644 index 78745856ce..0000000000 --- a/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridAllocations/MetricsPersonalGridAllocations.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import React, { FC, memo, useMemo } from 'react'; -import { useTranslation } from 'react-i18next'; - -import MetricsGridTile from 'components/Metrics/MetricsGrid/MetricsGridTile'; -import MetricsProjectsList from 'components/Metrics/MetricsProjectsList'; -import Img from 'components/ui/Img'; -import useUserAllocationsAllEpochs from 'hooks/helpers/useUserAllocationsAllEpochs'; - -import styles from './MetricsPersonalGridAllocations.module.scss'; -import MetricsPersonalGridAllocationsProps from './types'; -import { getReducedUserAllocationsAllEpochs } from './utils'; - -const MetricsPersonalGridAllocations: FC = ({ - isLoading, - size, -}) => { - const { t } = useTranslation('translation', { keyPrefix: 'views.metrics' }); - const { data: userAllocationsAllEpochs } = useUserAllocationsAllEpochs(); - const reducedUserAllocationsAllEpochs = - getReducedUserAllocationsAllEpochs(userAllocationsAllEpochs); - - const areAllocationsEmpty = !isLoading && reducedUserAllocationsAllEpochs?.length === 0; - - const children = useMemo(() => { - if (areAllocationsEmpty) { - return ( -
- -
{t('noAllocationsYet')}
-
- ); - } - - return ( - - ); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isLoading, areAllocationsEmpty, reducedUserAllocationsAllEpochs?.length, t]); - - return ( - - {reducedUserAllocationsAllEpochs.length} -
- ), - }, - ]} - size={size} - /> - ); -}; - -export default memo(MetricsPersonalGridAllocations); diff --git a/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridAllocations/index.tsx b/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridAllocations/index.tsx deleted file mode 100644 index 2cfc27b371..0000000000 --- a/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridAllocations/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line no-restricted-exports -export { default } from './MetricsPersonalGridAllocations'; diff --git a/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridAllocations/types.ts b/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridAllocations/types.ts deleted file mode 100644 index dfe4d9b67e..0000000000 --- a/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridAllocations/types.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { MetricsGridTileSizes } from 'components/Metrics/MetricsGrid/MetricsGridTile/types'; - -export default interface MetricsPersonalGridAllocationsProps { - isLoading: boolean; - size: MetricsGridTileSizes; -} diff --git a/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridAllocations/utils.test.ts b/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridAllocations/utils.test.ts deleted file mode 100644 index a73583f3e7..0000000000 --- a/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridAllocations/utils.test.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { parseUnitsBigInt } from 'utils/parseUnitsBigInt'; - -import { getReducedUserAllocationsAllEpochs } from './utils'; - -describe('getReducedUserAllocationsAllEpochs', () => { - it('properly reduces userAllocationsAllEpochs and squashes the duplicates, summarizing', () => { - expect( - getReducedUserAllocationsAllEpochs([ - { - elements: [ - { - address: '0x1', - epoch: 1, - value: parseUnitsBigInt('0.1'), - }, - { - address: '0x2', - epoch: 1, - value: parseUnitsBigInt('0.2'), - }, - { - address: '0x3', - epoch: 1, - value: parseUnitsBigInt('0.3'), - }, - { - address: '0x4', - epoch: 1, - value: parseUnitsBigInt('0.4'), - }, - ], - hasUserAlreadyDoneAllocation: true, - isManuallyEdited: false, - }, - { - elements: [ - { - address: '0x1', - epoch: 2, - value: parseUnitsBigInt('0.3'), - }, - { - address: '0x2', - epoch: 2, - value: parseUnitsBigInt('0.2'), - }, - { - address: '0x3', - epoch: 2, - value: parseUnitsBigInt('0.1'), - }, - { - address: '0x5', - epoch: 2, - value: parseUnitsBigInt('0.5'), - }, - ], - hasUserAlreadyDoneAllocation: true, - isManuallyEdited: false, - }, - ]), - ).toEqual([ - { - address: '0x1', - epoch: 2, - value: parseUnitsBigInt('0.4'), - }, - { - address: '0x2', - epoch: 2, - value: parseUnitsBigInt('0.4'), - }, - { - address: '0x3', - epoch: 2, - value: parseUnitsBigInt('0.4'), - }, - { - address: '0x4', - epoch: 1, - value: parseUnitsBigInt('0.4'), - }, - { - address: '0x5', - epoch: 2, - value: parseUnitsBigInt('0.5'), - }, - ]); - }); -}); diff --git a/client/src/components/Metrics/MetricsProjectsList/MetricsProjectsList.module.scss b/client/src/components/Metrics/MetricsProjectsList/MetricsProjectsList.module.scss index c61bbfdc2e..dafac25fb6 100644 --- a/client/src/components/Metrics/MetricsProjectsList/MetricsProjectsList.module.scss +++ b/client/src/components/Metrics/MetricsProjectsList/MetricsProjectsList.module.scss @@ -14,9 +14,9 @@ } .projectsList { - padding: 0 3.8rem 0rem 2.4rem; + display: grid; + padding: 0 2.4rem; max-height: 100%; overflow: auto; - margin-right: 0.8rem; } } diff --git a/client/src/components/Metrics/MetricsProjectsList/MetricsProjectsList.tsx b/client/src/components/Metrics/MetricsProjectsList/MetricsProjectsList.tsx index efa9c36262..8664d99f50 100644 --- a/client/src/components/Metrics/MetricsProjectsList/MetricsProjectsList.tsx +++ b/client/src/components/Metrics/MetricsProjectsList/MetricsProjectsList.tsx @@ -28,15 +28,40 @@ const MetricsProjectsList: FC = ({ key={project.address} address={project.address} dataTest={`${dataTest}__item`} + donations={ + getValuesToDisplay({ + cryptoCurrency: 'ethereum', + getFormattedEthValueProps: { + shouldIgnoreGwei: true, + shouldIgnoreWei: true, + }, + showCryptoSuffix: true, + showFiatPrefix: true, + valueCrypto: project.donations, + }).primary + } epoch={project.epoch} - value={ + matchFunding={ + getValuesToDisplay({ + cryptoCurrency: 'ethereum', + getFormattedEthValueProps: { + shouldIgnoreGwei: true, + shouldIgnoreWei: true, + }, + showCryptoSuffix: true, + valueCrypto: project.matchedRewards, + }).primary + } + numberOfDonors={project.numberOfDonors} + total={ getValuesToDisplay({ cryptoCurrency: 'ethereum', getFormattedEthValueProps: { shouldIgnoreGwei: true, shouldIgnoreWei: true, }, - valueCrypto: project.value, + showCryptoSuffix: true, + valueCrypto: project.totalValueOfAllocations, }).primary } /> diff --git a/client/src/components/Metrics/MetricsProjectsList/types.ts b/client/src/components/Metrics/MetricsProjectsList/types.ts index 2e228aaf03..a3e842195e 100644 --- a/client/src/components/Metrics/MetricsProjectsList/types.ts +++ b/client/src/components/Metrics/MetricsProjectsList/types.ts @@ -1,8 +1,8 @@ -import { ResponseItem } from 'hooks/helpers/useUserAllocationsAllEpochs'; +import { ProjectIpfsWithRewards } from 'hooks/queries/useProjectsIpfsWithRewards'; export default interface MetricsProjectsListProps { dataTest?: string; isLoading: boolean; numberOfSkeletons: number; - projects: ResponseItem['elements']; + projects: (ProjectIpfsWithRewards & { epoch: number })[]; } diff --git a/client/src/components/Metrics/MetricsProjectsListItem/MetricsProjectsListItem.module.scss b/client/src/components/Metrics/MetricsProjectsListItem/MetricsProjectsListItem.module.scss index dbc6f9f717..0831cf5d11 100644 --- a/client/src/components/Metrics/MetricsProjectsListItem/MetricsProjectsListItem.module.scss +++ b/client/src/components/Metrics/MetricsProjectsListItem/MetricsProjectsListItem.module.scss @@ -1,13 +1,25 @@ .root { - display: flex; + display: grid; + // display: flex; align-items: center; justify-content: space-between; height: 5.6rem; + grid-template-columns: auto auto; + + @media #{$large-desktop-up} { + grid-template-columns: 21.2rem 8rem 11.8rem 11.8rem 11.8rem; + } &:not(:last-child) { border-bottom: 0.1rem solid $color-octant-grey3; } + .logoNameGroup { + display: flex; + align-items: center; + overflow: hidden; + } + .image { height: 2.4rem; width: 2.4rem; @@ -19,19 +31,31 @@ .value { font-size: $font-size-12; font-weight: $font-weight-bold; - color: $color-octant-dark; } .name { width: 100%; - text-align: left; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; + color: $color-octant-dark; + margin-right: 0.8rem; + text-align: left; } .value { - margin-left: 1rem; + margin-left: 0.2rem; flex-shrink: 0; + color: $color-octant-grey5; + text-align: right; + white-space: nowrap; + + @media #{$large-desktop-up} { + text-align: left; + } + + &.total { + color: $color-octant-dark; + } } } diff --git a/client/src/components/Metrics/MetricsProjectsListItem/MetricsProjectsListItem.tsx b/client/src/components/Metrics/MetricsProjectsListItem/MetricsProjectsListItem.tsx index 85da1968b5..5b1dd7e38d 100644 --- a/client/src/components/Metrics/MetricsProjectsListItem/MetricsProjectsListItem.tsx +++ b/client/src/components/Metrics/MetricsProjectsListItem/MetricsProjectsListItem.tsx @@ -1,7 +1,9 @@ +import cx from 'classnames'; import React, { FC, memo } from 'react'; import Img from 'components/ui/Img/Img'; import env from 'env'; +import useMediaQuery from 'hooks/helpers/useMediaQuery'; import useProjectsIpfs from 'hooks/queries/useProjectsIpfs'; import styles from './MetricsProjectsListItem.module.scss'; @@ -10,27 +12,46 @@ import MetricsProjectsListItemProps from './types'; const MetricsProjectsListItem: FC = ({ address, epoch, - value, + numberOfDonors, + donations, + matchFunding, + total, dataTest = 'MetricsProjectsListItem', }) => { const { ipfsGateways } = env; const { data: projectsIpfs } = useProjectsIpfs([address], epoch); + const { isLargeDesktop } = useMediaQuery(); const image = projectsIpfs.at(0)?.profileImageSmall; const name = projectsIpfs.at(0)?.name; return (
- project logo `${element}${image}`)} - /> -
- {name} +
+ project logo `${element}${image}`)} + /> +
+ {name} +
-
- {value} + {isLargeDesktop && ( + <> +
+ {numberOfDonors} +
+
+ {donations} +
+
+ {matchFunding} +
+ + )} +
+ {total}
); diff --git a/client/src/components/Metrics/MetricsProjectsListItem/types.ts b/client/src/components/Metrics/MetricsProjectsListItem/types.ts index c87fc4fac1..251024fdb1 100644 --- a/client/src/components/Metrics/MetricsProjectsListItem/types.ts +++ b/client/src/components/Metrics/MetricsProjectsListItem/types.ts @@ -1,6 +1,9 @@ export default interface MetricsProjectsListItemProps { address: string; dataTest?: string; + donations: string; epoch?: number; - value: string; + matchFunding: string; + numberOfDonors: number; + total: string; } diff --git a/client/src/components/Metrics/MetricsProjectsListSkeletonItem/MetricsProjectsListSkeletonItem.module.scss b/client/src/components/Metrics/MetricsProjectsListSkeletonItem/MetricsProjectsListSkeletonItem.module.scss index b11934a8e5..6921abf7d5 100644 --- a/client/src/components/Metrics/MetricsProjectsListSkeletonItem/MetricsProjectsListSkeletonItem.module.scss +++ b/client/src/components/Metrics/MetricsProjectsListSkeletonItem/MetricsProjectsListSkeletonItem.module.scss @@ -1,34 +1,47 @@ .root { - display: flex; + display: grid; align-items: center; justify-content: space-between; height: 5.6rem; + grid-template-columns: 16rem 8rem; + + @media #{$large-desktop-up} { + grid-template-columns: 21.2rem 8rem 11.8rem 11.8rem 11.8rem; + } &:not(:last-child) { border-bottom: 0.1rem solid $color-octant-grey3; } - .image { - @include skeleton(); - height: 2.4rem; - width: 2.4rem; - border-radius: 100%; - margin-right: 1.6rem; + .imageNameGroup { + display: flex; + align-items: center; + + .image { + @include skeleton(); + height: 2.4rem; + width: 2.4rem; + border-radius: 100%; + margin-right: 1.6rem; + min-width: 2.4rem; + } + + .name { + @include skeleton(); + height: 1.5rem; + width: 16.4rem; + } } - .name { + .numberOfDonors { @include skeleton(); height: 1.5rem; - width: 18rem; + width: calc(100% - 1rem); } .value { @include skeleton(); height: 1.5rem; - width: 4rem; - } - - .value { - margin-left: auto; + width: calc(100% - 1rem); } } diff --git a/client/src/components/Metrics/MetricsProjectsListSkeletonItem/MetricsProjectsListSkeletonItem.tsx b/client/src/components/Metrics/MetricsProjectsListSkeletonItem/MetricsProjectsListSkeletonItem.tsx index 1b7197f451..6cf2df6a45 100644 --- a/client/src/components/Metrics/MetricsProjectsListSkeletonItem/MetricsProjectsListSkeletonItem.tsx +++ b/client/src/components/Metrics/MetricsProjectsListSkeletonItem/MetricsProjectsListSkeletonItem.tsx @@ -1,12 +1,24 @@ import React, { ReactElement, memo } from 'react'; +import useMediaQuery from 'hooks/helpers/useMediaQuery'; + import styles from './MetricsProjectsListSkeletonItem.module.scss'; const MetricsProjectsListSkeletonItem = (): ReactElement => { + const { isLargeDesktop } = useMediaQuery(); return (
-
-
+
+
+
+
+ {isLargeDesktop && ( + <> +
+
+
+ + )}
); diff --git a/client/src/hooks/queries/useProjectsIpfsWithRewards.ts b/client/src/hooks/queries/useProjectsIpfsWithRewards.ts index e38398c5b1..32ca541d5e 100644 --- a/client/src/hooks/queries/useProjectsIpfsWithRewards.ts +++ b/client/src/hooks/queries/useProjectsIpfsWithRewards.ts @@ -8,9 +8,11 @@ import useProjectsIpfs from './useProjectsIpfs'; export interface ProjectIpfsWithRewards extends ExtendedProject { address: string; + donations: bigint; + matchedRewards: bigint; numberOfDonors: number; percentage: number | undefined; - totalValueOfAllocations: bigint | undefined; + totalValueOfAllocations: bigint; } export default function useProjectsIpfsWithRewards(epoch?: number): { @@ -58,16 +60,17 @@ export default function useProjectsIpfsWithRewards(epoch?: number): { * For epochs finalized projectMatchedProjectRewards contains data only for projects that * passed threshold. For those that did not, we reduce on their donors and get the value. */ - const totalValueOfAllocations = - projectMatchedProjectRewards?.sum || - (isSuccessProjectsDonors - ? projectsDonors[project.address]?.reduce((acc, curr) => acc + curr.amount, BigInt(0)) || - BigInt(0) - : BigInt(0)); + const donations = isSuccessProjectsDonors + ? projectsDonors[project.address]?.reduce((acc, curr) => acc + curr.amount, BigInt(0)) || + BigInt(0) + : BigInt(0); + return { + donations, + matchedRewards: projectMatchedProjectRewards?.matched || 0n, numberOfDonors: isSuccessProjectsDonors ? projectsDonors[project.address]?.length || 0 : 0, percentage: projectMatchedProjectRewards?.percentage, - totalValueOfAllocations, + totalValueOfAllocations: donations + (projectMatchedProjectRewards?.matched ?? 0n), ...project, }; }); diff --git a/client/src/locales/en/translation.json b/client/src/locales/en/translation.json index 5a4cb8d075..14bd58f8fe 100644 --- a/client/src/locales/en/translation.json +++ b/client/src/locales/en/translation.json @@ -554,6 +554,11 @@ } }, "metrics": { + "fundingLeaderboard": "Funding leaderboard", + "donors": "Donors", + "donations": "Donations", + "matchFunding": "Match funding", + "total": "Total", "donatedToProjects": "Donated to projects", "claimedByUsers": "Claimed by users", "open": "Open", diff --git a/client/src/mocks/subgraph/projects.ts b/client/src/mocks/subgraph/projects.ts index 24220f869b..e921042e5a 100644 --- a/client/src/mocks/subgraph/projects.ts +++ b/client/src/mocks/subgraph/projects.ts @@ -2,7 +2,9 @@ import { ProjectIpfsWithRewards } from 'hooks/queries/useProjectsIpfsWithRewards export const mockedProjectATotalValueOfAllocations1: ProjectIpfsWithRewards = { address: 'address', + donations: BigInt(1), isLoadingError: false, + matchedRewards: BigInt(1), name: 'A', numberOfDonors: 10, percentage: 1, diff --git a/client/src/utils/getSortedElementsByTotalValueOfAllocationsAndAlphabetical.test.ts b/client/src/utils/getSortedElementsByTotalValueOfAllocationsAndAlphabetical.test.ts index f8155e5923..452a2aea82 100644 --- a/client/src/utils/getSortedElementsByTotalValueOfAllocationsAndAlphabetical.test.ts +++ b/client/src/utils/getSortedElementsByTotalValueOfAllocationsAndAlphabetical.test.ts @@ -1,3 +1,4 @@ +import { ProjectIpfsWithRewards } from 'hooks/queries/useProjectsIpfsWithRewards'; import { mockedProjectATotalValueOfAllocations1, mockedProjectBTotalValueOfAllocations2, @@ -84,9 +85,11 @@ const testCases = [ describe('getSortedElementsByTotalValueOfAllocationsAndAlphabetical', () => { for (const { argument, expectedValue } of testCases) { it('properly returns expectedValue', () => { - expect(getSortedElementsByTotalValueOfAllocationsAndAlphabetical(argument)).toEqual( - expectedValue, - ); + expect( + getSortedElementsByTotalValueOfAllocationsAndAlphabetical( + argument as ProjectIpfsWithRewards[], + ), + ).toEqual(expectedValue); }); } }); diff --git a/client/src/views/MetricsView/MetricsView.tsx b/client/src/views/MetricsView/MetricsView.tsx index 9469e8fe8b..3fd32135f3 100644 --- a/client/src/views/MetricsView/MetricsView.tsx +++ b/client/src/views/MetricsView/MetricsView.tsx @@ -2,7 +2,6 @@ import React, { ReactElement, useLayoutEffect } from 'react'; import MetricsEpoch from 'components/Metrics/MetricsEpoch'; import MetricsGeneral from 'components/Metrics/MetricsGeneral/MetricsGeneral'; -import MetricsNavigation from 'components/Metrics/MetricsNavigation'; import MetricsPersonal from 'components/Metrics/MetricsPersonal'; import { MetricsEpochProvider } from 'hooks/helpers/useMetrcisEpoch'; import useCurrentEpoch from 'hooks/queries/useCurrentEpoch'; @@ -22,7 +21,6 @@ const MetricsView = (): ReactElement => { "It's Epoch 1, so there are no metrics for the past. It's just a placeholder, please come back in Epoch 2." ) : ( <> - From aa734d2567f459f0460e8d8d4454660815ab1523 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Miko=C5=82ajczyk?= Date: Wed, 18 Sep 2024 18:32:20 +0200 Subject: [PATCH 098/241] project admin + patron mode cr fixes --- .../HomeGridPersonalAllocation.tsx | 4 ++-- .../ProjectListItemHeader.tsx | 15 ++++++--------- .../ProjectsListItem/ProjectsListItem.tsx | 15 ++++++--------- .../ModalSettingsPatronMode.tsx | 6 ++---- client/src/components/shared/Layout/Layout.tsx | 1 + .../Layout/LayoutTopBar/LayoutTopBar.tsx | 2 +- client/src/components/ui/Modal/Modal.tsx | 6 ++++-- .../helpers/useIsAddToAllocateButtonVisible.ts | 18 ++++++++++++++++++ 8 files changed, 40 insertions(+), 27 deletions(-) create mode 100644 client/src/hooks/helpers/useIsAddToAllocateButtonVisible.ts diff --git a/client/src/components/Home/HomeGridPersonalAllocation/HomeGridPersonalAllocation.tsx b/client/src/components/Home/HomeGridPersonalAllocation/HomeGridPersonalAllocation.tsx index 3fd3bf3edd..5c2759a87c 100644 --- a/client/src/components/Home/HomeGridPersonalAllocation/HomeGridPersonalAllocation.tsx +++ b/client/src/components/Home/HomeGridPersonalAllocation/HomeGridPersonalAllocation.tsx @@ -65,10 +65,10 @@ const HomeGridPersonalAllocation: FC = ({ class hasBottomDivider sections={[ { - dataTest: 'BoxPersonalAllocation__Section', + dataTest: 'HomeGridPersonalAllocation__Section', doubleValueProps: { cryptoCurrency: 'ethereum', - dataTest: 'BoxPersonalAllocation__Section--pending__DoubleValue', + dataTest: 'HomeGridPersonalAllocation__Section--pending__DoubleValue', isFetching: isFetchingWithdrawals || (isAppWaitingForTransactionToBeIndexed && diff --git a/client/src/components/Project/ProjectListItemHeader/ProjectListItemHeader.tsx b/client/src/components/Project/ProjectListItemHeader/ProjectListItemHeader.tsx index ead51deb55..051c9e7b69 100644 --- a/client/src/components/Project/ProjectListItemHeader/ProjectListItemHeader.tsx +++ b/client/src/components/Project/ProjectListItemHeader/ProjectListItemHeader.tsx @@ -10,10 +10,9 @@ import Svg from 'components/ui/Svg'; import Tooltip from 'components/ui/Tooltip'; import env from 'env'; import useIdsInAllocation from 'hooks/helpers/useIdsInAllocation'; -import useIsProjectAdminMode from 'hooks/helpers/useIsProjectAdminMode'; +import useIsAddToAllocateButtonVisible from 'hooks/helpers/useIsAddToAllocateButtonVisible'; import useCurrentEpoch from 'hooks/queries/useCurrentEpoch'; import useIsDecisionWindowOpen from 'hooks/queries/useIsDecisionWindowOpen'; -import useIsPatronMode from 'hooks/queries/useIsPatronMode'; import useUserAllocations from 'hooks/queries/useUserAllocations'; import { ROOT_ROUTES } from 'routes/RootRoutes/routes'; import useAllocationsStore from 'store/allocations/store'; @@ -36,8 +35,6 @@ const ProjectListItemHeader: FC = ({ const { data: currentEpoch } = useCurrentEpoch(); const { data: userAllocations } = useUserAllocations(epoch); const { data: isDecisionWindowOpen } = useIsDecisionWindowOpen(); - const isProjectAdminMode = useIsProjectAdminMode(); - const { data: isPatronMode } = useIsPatronMode(); const { allocations, addAllocations, removeAllocations } = useAllocationsStore(state => ({ addAllocations: state.addAllocations, allocations: state.data.allocations, @@ -58,10 +55,10 @@ const ProjectListItemHeader: FC = ({ ({ address: userAllocationAddress }) => userAllocationAddress === address, ); - const showAddToAllocateButton = - !isProjectAdminMode && - !isPatronMode && - ((isAllocatedTo && isArchivedProject) || !isArchivedProject); + const isAddToAllocateButtonVisible = useIsAddToAllocateButtonVisible({ + isAllocatedTo, + isArchivedProject, + }); const onShareClick = (): boolean | Promise => { const { origin } = window.location; @@ -110,7 +107,7 @@ const ProjectListItemHeader: FC = ({ size={3.2} /> - {showAddToAllocateButton && ( + {isAddToAllocateButtonVisible && ( = ({ const navigate = useNavigate(); const { data: userAllocations } = useUserAllocations(epoch); const { data: isDecisionWindowOpen } = useIsDecisionWindowOpen(); - const isProjectAdminMode = useIsProjectAdminMode(); - const { data: isPatronMode } = useIsPatronMode(); const { allocations, addAllocations, removeAllocations } = useAllocationsStore(state => ({ addAllocations: state.addAllocations, allocations: state.data.allocations, @@ -68,10 +65,10 @@ const ProjectsListItem: FC = ({ const isEpoch1 = currentEpoch === 1; const isArchivedProject = epoch !== undefined; - const showAddToAllocateButton = - !isProjectAdminMode && - !isPatronMode && - ((isAllocatedTo && isArchivedProject) || !isArchivedProject); + const isAddToAllocateButtonVisible = useIsAddToAllocateButtonVisible({ + isAllocatedTo, + isArchivedProject, + }); return (
= ({ } sources={ipfsGateways.split(',').map(element => `${element}${profileImageSmall}`)} /> - {showAddToAllocateButton && ( + {isAddToAllocateButtonVisible && ( = ({ modalProps // eslint-disable-next-line react-hooks/exhaustive-deps const isPatronModeEnabled = useMemo(() => isPatronMode, [modalProps.isOpen]); - return createPortal( + return ( = ({ modalProps className={styles.root} > - , - document.body, + ); }; diff --git a/client/src/components/shared/Layout/Layout.tsx b/client/src/components/shared/Layout/Layout.tsx index 7a3478c3d4..268d617406 100644 --- a/client/src/components/shared/Layout/Layout.tsx +++ b/client/src/components/shared/Layout/Layout.tsx @@ -157,6 +157,7 @@ const Layout: FC = ({ // // eslint-disable-next-line react-hooks/exhaustive-deps // }, [isDecisionWindowOpen, timeCurrentAllocationEnd, timeCurrentEpochEnd]); + // Logic that hides TopBar when scrolling down and shows when scrolling up (only on mobile devices) useEffect(() => { if (!topBarWrapperRef?.current) { return; diff --git a/client/src/components/shared/Layout/LayoutTopBar/LayoutTopBar.tsx b/client/src/components/shared/Layout/LayoutTopBar/LayoutTopBar.tsx index 6bc89d04ea..2b6f279e22 100644 --- a/client/src/components/shared/Layout/LayoutTopBar/LayoutTopBar.tsx +++ b/client/src/components/shared/Layout/LayoutTopBar/LayoutTopBar.tsx @@ -198,7 +198,7 @@ const LayoutTopBar: FC = ({ className }) => {
{allocations.length}
diff --git a/client/src/components/ui/Modal/Modal.tsx b/client/src/components/ui/Modal/Modal.tsx index 3fd8597c0f..2619cebe0d 100644 --- a/client/src/components/ui/Modal/Modal.tsx +++ b/client/src/components/ui/Modal/Modal.tsx @@ -1,6 +1,7 @@ import cx from 'classnames'; import { motion, AnimatePresence } from 'framer-motion'; import React, { FC, useEffect } from 'react'; +import { createPortal } from 'react-dom'; import Button from 'components/ui/Button'; import Svg from 'components/ui/Svg'; @@ -75,7 +76,7 @@ const Modal: FC = ({ */ useEffect(() => () => setDocumentOverflowModal(false, durationOfTransition * 1000), []); - return ( + return createPortal( {isOverflowEnabled && isOpen && ( = ({ )} )} - + , + document.body, ); }; diff --git a/client/src/hooks/helpers/useIsAddToAllocateButtonVisible.ts b/client/src/hooks/helpers/useIsAddToAllocateButtonVisible.ts new file mode 100644 index 0000000000..c61752d048 --- /dev/null +++ b/client/src/hooks/helpers/useIsAddToAllocateButtonVisible.ts @@ -0,0 +1,18 @@ +import useIsProjectAdminMode from 'hooks/helpers/useIsProjectAdminMode'; +import useIsPatronMode from 'hooks/queries/useIsPatronMode'; + +type UseIsAddToAllocateButtonVisibleProps = { isAllocatedTo: boolean; isArchivedProject: boolean }; + +export default function useIsAddToAllocateButtonVisible({ + isAllocatedTo, + isArchivedProject, +}: UseIsAddToAllocateButtonVisibleProps): boolean { + const isProjectAdminMode = useIsProjectAdminMode(); + const { data: isPatronMode } = useIsPatronMode(); + + return ( + !isProjectAdminMode && + !isPatronMode && + ((isAllocatedTo && isArchivedProject) || !isArchivedProject) + ); +} From 86a217eab8531f4077abde68be184d7c8ee80f36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Zi=C3=B3=C5=82ek?= Date: Thu, 19 Sep 2024 13:50:32 +0200 Subject: [PATCH 099/241] fix: ProjectsView random s character --- client/src/views/ProjectsView/ProjectsView.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/client/src/views/ProjectsView/ProjectsView.tsx b/client/src/views/ProjectsView/ProjectsView.tsx index 878f1f0663..2e85efdb91 100644 --- a/client/src/views/ProjectsView/ProjectsView.tsx +++ b/client/src/views/ProjectsView/ProjectsView.tsx @@ -160,7 +160,6 @@ const ProjectsView = (): ReactElement => { variant="underselect" />
- s {!areCurrentEpochsProjectsHiddenOutsideAllocationWindow && ( Date: Thu, 19 Sep 2024 16:10:25 +0200 Subject: [PATCH 100/241] oct-1883: metrics update --- .../MetricsDonationsProgressBar.tsx | 3 +- .../MetricsDonationsProgressBar/types.ts | 1 + .../MetricsEpoch/MetricsEpoch.module.scss | 47 +++++++---- .../Metrics/MetricsEpoch/MetricsEpoch.tsx | 56 ++++++++----- .../MetricsEpochGridDonationsVsMatching.tsx | 50 +++++++++++ .../index.tsx | 2 + .../types.ts | 6 ++ ...pochGridDonationsVsPersonalAllocations.tsx | 8 +- .../MetricsEpochGridRewardsUnused.tsx | 44 ++++++++++ .../MetricsEpochGridRewardsUnused/index.tsx | 2 + .../MetricsEpochGridRewardsUnused/types.ts | 4 + ...wardsUnusedAndUnallocatedValue.module.scss | 5 -- .../index.tsx | 2 - .../types.ts | 4 - .../MetricsEpochGridTotalDonations.tsx} | 36 +++----- .../MetricsEpochGridTotalDonations/index.tsx | 2 + .../MetricsEpochGridTotalDonations/types.ts | 5 ++ ...hGridTotalDonationsAndPersonal.module.scss | 5 -- .../index.tsx | 2 - .../types.ts | 6 -- .../MetricsEpochGridTotalMatchingFund.tsx | 47 +++++++++++ .../index.tsx | 2 + .../types.ts | 5 ++ .../MetricsEpochGridUnallocatedValue.tsx} | 29 ++----- .../index.tsx | 2 + .../MetricsEpochGridUnallocatedValue/types.ts | 4 + .../MetricsEpochHeader/MetricsEpochHeader.tsx | 8 +- .../MetricsGeneral/MetricsGeneral.module.scss | 17 ++++ .../Metrics/MetricsGeneral/MetricsGeneral.tsx | 11 ++- ...GeneralGridCumulativeGlmLocked.module.scss | 7 +- .../MetricsGeneralGridCumulativeGlmLocked.tsx | 3 +- ...eneralGridWalletsWithGlmLocked.module.scss | 7 +- ...MetricsGeneralGridWalletsWithGlmLocked.tsx | 3 +- .../MetricsGridTile.module.scss | 11 ++- .../MetricsGridTile/MetricsGridTile.tsx | 2 +- .../MetricsGrid/MetricsGridTile/types.ts | 2 +- .../MetricsGridTileTimeSlicer.module.scss | 1 + .../MetricsHeader/MetricsHeader.module.scss | 14 ---- .../Metrics/MetricsHeader/MetricsHeader.tsx | 21 ----- .../MetricsPersonal.module.scss | 18 ---- .../MetricsPersonal/MetricsPersonal.tsx | 83 ------------------- ...etricsPersonalGridDonationsProgressBar.tsx | 48 ----------- .../index.tsx | 2 - .../types.ts | 3 - .../MetricsPersonalGridPatronDonations.tsx | 56 ------------- .../index.tsx | 2 - .../types.ts | 5 -- ...icsPersonalGridTotalRewardsWithdrawals.tsx | 63 -------------- .../index.tsx | 2 - .../types.ts | 3 - .../Metrics/MetricsPersonal/index.tsx | 2 - .../MetricsSectionHeader.module.scss | 13 +++ .../MetricsSectionHeader.tsx | 19 +++++ .../index.ts | 2 +- .../types.ts | 2 +- client/src/locales/en/translation.json | 7 +- client/src/styles/utils/_typography.scss | 2 + .../views/MetricsView/MetricsView.module.scss | 23 ++++- client/src/views/MetricsView/MetricsView.tsx | 7 +- 59 files changed, 388 insertions(+), 460 deletions(-) create mode 100644 client/src/components/Metrics/MetricsEpoch/MetricsEpochGridDonationsVsMatching/MetricsEpochGridDonationsVsMatching.tsx create mode 100644 client/src/components/Metrics/MetricsEpoch/MetricsEpochGridDonationsVsMatching/index.tsx create mode 100644 client/src/components/Metrics/MetricsEpoch/MetricsEpochGridDonationsVsMatching/types.ts create mode 100644 client/src/components/Metrics/MetricsEpoch/MetricsEpochGridRewardsUnused/MetricsEpochGridRewardsUnused.tsx create mode 100644 client/src/components/Metrics/MetricsEpoch/MetricsEpochGridRewardsUnused/index.tsx create mode 100644 client/src/components/Metrics/MetricsEpoch/MetricsEpochGridRewardsUnused/types.ts delete mode 100644 client/src/components/Metrics/MetricsEpoch/MetricsEpochGridRewardsUnusedAndUnallocatedValue/MetricsEpochGridRewardsUnusedAndUnallocatedValue.module.scss delete mode 100644 client/src/components/Metrics/MetricsEpoch/MetricsEpochGridRewardsUnusedAndUnallocatedValue/index.tsx delete mode 100644 client/src/components/Metrics/MetricsEpoch/MetricsEpochGridRewardsUnusedAndUnallocatedValue/types.ts rename client/src/components/Metrics/MetricsEpoch/{MetricsEpochGridTotalDonationsAndPersonal/MetricsEpochGridTotalDonationsAndPersonal.tsx => MetricsEpochGridTotalDonations/MetricsEpochGridTotalDonations.tsx} (52%) create mode 100644 client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalDonations/index.tsx create mode 100644 client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalDonations/types.ts delete mode 100644 client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalDonationsAndPersonal/MetricsEpochGridTotalDonationsAndPersonal.module.scss delete mode 100644 client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalDonationsAndPersonal/index.tsx delete mode 100644 client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalDonationsAndPersonal/types.ts create mode 100644 client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalMatchingFund/MetricsEpochGridTotalMatchingFund.tsx create mode 100644 client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalMatchingFund/index.tsx create mode 100644 client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalMatchingFund/types.ts rename client/src/components/Metrics/MetricsEpoch/{MetricsEpochGridRewardsUnusedAndUnallocatedValue/MetricsEpochGridRewardsUnusedAndUnallocatedValue.tsx => MetricsEpochGridUnallocatedValue/MetricsEpochGridUnallocatedValue.tsx} (60%) create mode 100644 client/src/components/Metrics/MetricsEpoch/MetricsEpochGridUnallocatedValue/index.tsx create mode 100644 client/src/components/Metrics/MetricsEpoch/MetricsEpochGridUnallocatedValue/types.ts delete mode 100644 client/src/components/Metrics/MetricsHeader/MetricsHeader.module.scss delete mode 100644 client/src/components/Metrics/MetricsHeader/MetricsHeader.tsx delete mode 100644 client/src/components/Metrics/MetricsPersonal/MetricsPersonal.module.scss delete mode 100644 client/src/components/Metrics/MetricsPersonal/MetricsPersonal.tsx delete mode 100644 client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridDonationsProgressBar/MetricsPersonalGridDonationsProgressBar.tsx delete mode 100644 client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridDonationsProgressBar/index.tsx delete mode 100644 client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridDonationsProgressBar/types.ts delete mode 100644 client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridPatronDonations/MetricsPersonalGridPatronDonations.tsx delete mode 100644 client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridPatronDonations/index.tsx delete mode 100644 client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridPatronDonations/types.ts delete mode 100644 client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridTotalRewardsWithdrawals/MetricsPersonalGridTotalRewardsWithdrawals.tsx delete mode 100644 client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridTotalRewardsWithdrawals/index.tsx delete mode 100644 client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridTotalRewardsWithdrawals/types.ts delete mode 100644 client/src/components/Metrics/MetricsPersonal/index.tsx create mode 100644 client/src/components/Metrics/MetricsSectionHeader/MetricsSectionHeader.module.scss create mode 100644 client/src/components/Metrics/MetricsSectionHeader/MetricsSectionHeader.tsx rename client/src/components/Metrics/{MetricsHeader => MetricsSectionHeader}/index.ts (50%) rename client/src/components/Metrics/{MetricsHeader => MetricsSectionHeader}/types.ts (65%) diff --git a/client/src/components/Metrics/MetricsDonationsProgressBar/MetricsDonationsProgressBar.tsx b/client/src/components/Metrics/MetricsDonationsProgressBar/MetricsDonationsProgressBar.tsx index 1d80f63fad..63f4f79e66 100644 --- a/client/src/components/Metrics/MetricsDonationsProgressBar/MetricsDonationsProgressBar.tsx +++ b/client/src/components/Metrics/MetricsDonationsProgressBar/MetricsDonationsProgressBar.tsx @@ -12,6 +12,7 @@ const MetricsDonationsProgressBar: FC = ({ isDisabled, isLoading, donationsValue, + compareValueLabel, }) => { const { i18n } = useTranslation('translation'); const donationsPercentage = isDisabled ? 0 : donationsValue.toFixed(2).replace(dotAndZeroes, ''); @@ -50,7 +51,7 @@ const MetricsDonationsProgressBar: FC = ({ ) : ( <>
{personalPercentage}%
-
{i18n.t('common.personal')}
+
{compareValueLabel}
)}
diff --git a/client/src/components/Metrics/MetricsDonationsProgressBar/types.ts b/client/src/components/Metrics/MetricsDonationsProgressBar/types.ts index 9ba1096633..9472adcc49 100644 --- a/client/src/components/Metrics/MetricsDonationsProgressBar/types.ts +++ b/client/src/components/Metrics/MetricsDonationsProgressBar/types.ts @@ -1,4 +1,5 @@ export default interface MetricsDonationsProgressBarProps { + compareValueLabel: string; donationsValue: number; isDisabled?: boolean; isLoading: boolean; diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpoch.module.scss b/client/src/components/Metrics/MetricsEpoch/MetricsEpoch.module.scss index 5ae2ca6865..96c2c82403 100644 --- a/client/src/components/Metrics/MetricsEpoch/MetricsEpoch.module.scss +++ b/client/src/components/Metrics/MetricsEpoch/MetricsEpoch.module.scss @@ -1,67 +1,78 @@ .root { width: 100%; + border-top: 0.1rem solid $color-octant-grey1; + border-bottom: 0.1rem solid $color-octant-grey1; + padding-bottom: 6.4rem; } .grid { - grid-template-rows: 16rem 16rem minmax(14.4rem, auto); + grid-template-rows: repeat(10, 15.6rem); + + @media #{$tablet-up} { + grid-template-rows: repeat(6, 15.6rem); + } @media #{$desktop-up} { - grid-template-rows: 14.4rem 14.4rem minmax(14.4rem, auto); + grid-template-rows: repeat(4, 15.6rem); + } +} + +.topProjects, +.fundsUsage { + @media #{$tablet-down} { + order: 0; } } -.topProjects { +.totalDonations, +.totalMatching { @media #{$tablet-down} { order: 1; } } -.totalDonationsAndPersonal { + +.donationsVsMatching { @media #{$tablet-down} { order: 2; } } -.donationsVsPersonal { + +.totalUsers { @media #{$tablet-down} { order: 3; } } -.currentDonors { +.patrons { @media #{$tablet-down} { order: 4; } } -.averageLeverage { +.currentDonors { @media #{$tablet-down} { order: 5; } } -.totalUsers { +.averageLeverage { @media #{$tablet-down} { order: 6; } } -.patrons { + +.rewardsUnused { @media #{$tablet-down} { order: 7; } } - -.fundsUsage { +.unallocatedValue { @media #{$tablet-down} { order: 8; } } -.unusedAndUnallocatedValue { +.donationsVsPersonal { @media #{$tablet-down} { order: 9; } } - -.belowThreshold { - @media #{$tablet-down} { - order: 10; - } -} diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpoch.tsx b/client/src/components/Metrics/MetricsEpoch/MetricsEpoch.tsx index 54e71b2136..035b7f2f29 100644 --- a/client/src/components/Metrics/MetricsEpoch/MetricsEpoch.tsx +++ b/client/src/components/Metrics/MetricsEpoch/MetricsEpoch.tsx @@ -3,13 +3,16 @@ import React, { ReactElement } from 'react'; import MetricsEpochGridAverageLeverage from 'components/Metrics/MetricsEpoch/MetricsEpochGridAverageLeverage'; import MetricsEpochGridBelowThreshold from 'components/Metrics/MetricsEpoch/MetricsEpochGridBelowThreshold'; import MetricsEpochGridCurrentDonors from 'components/Metrics/MetricsEpoch/MetricsEpochGridCurrentDonors'; +import MetricsEpochGridDonationsVsMatching from 'components/Metrics/MetricsEpoch/MetricsEpochGridDonationsVsMatching'; import MetricsEpochGridDonationsVsPersonalAllocations from 'components/Metrics/MetricsEpoch/MetricsEpochGridDonationsVsPersonalAllocations'; import MetricsEpochGridFundsUsage from 'components/Metrics/MetricsEpoch/MetricsEpochGridFundsUsage'; import MetricsEpochGridPatrons from 'components/Metrics/MetricsEpoch/MetricsEpochGridPatrons'; -import MetricsEpochGridRewardsUnusedAndUnallocatedValue from 'components/Metrics/MetricsEpoch/MetricsEpochGridRewardsUnusedAndUnallocatedValue'; +import MetricsEpochGridRewardsUnused from 'components/Metrics/MetricsEpoch/MetricsEpochGridRewardsUnused'; import MetricsEpochGridTopProjects from 'components/Metrics/MetricsEpoch/MetricsEpochGridTopProjects'; -import MetricsEpochGridTotalDonationsAndPersonal from 'components/Metrics/MetricsEpoch/MetricsEpochGridTotalDonationsAndPersonal'; +import MetricsEpochGridTotalDonations from 'components/Metrics/MetricsEpoch/MetricsEpochGridTotalDonations'; +import MetricsEpochGridTotalMatchingFund from 'components/Metrics/MetricsEpoch/MetricsEpochGridTotalMatchingFund'; import MetricsEpochGridTotalUsers from 'components/Metrics/MetricsEpoch/MetricsEpochGridTotalUsers'; +import MetricsEpochGridUnallocatedValue from 'components/Metrics/MetricsEpoch/MetricsEpochGridUnallocatedValue'; import MetricsEpochHeader from 'components/Metrics/MetricsEpoch/MetricsEpochHeader'; import MetricsGrid from 'components/Metrics/MetricsGrid'; import { METRICS_EPOCH_ID } from 'constants/domElementsIds'; @@ -47,9 +50,8 @@ const MetricsEpoch = (): ReactElement => { const { isFetching: isFetchingMatchedProjectRewards } = useMatchedProjectRewards( isDecisionWindowOpen && epoch === lastEpoch ? undefined : epoch, ); - const { isFetching: isFetchingProjectsIpfsWithRewards } = useProjectsIpfsWithRewards( - isDecisionWindowOpen && epoch === lastEpoch ? undefined : epoch, - ); + const { data: projectsIpfsWithRewards, isFetching: isFetchingProjectsIpfsWithRewards } = + useProjectsIpfsWithRewards(isDecisionWindowOpen && epoch === lastEpoch ? undefined : epoch); const { data: projectsDonors, isFetching: isFetchingProjectsDonors } = useProjectsDonors( isDecisionWindowOpen && epoch === lastEpoch ? undefined : epoch, ); @@ -87,6 +89,11 @@ const MetricsEpoch = (): ReactElement => { const epochBudget = epochBudgets?.budgetsSum || BigInt(0); const totalPersonal = epochBudget - totalUserDonationsWithPatronRewards - unusedRewards; + const matchedRewards = projectsIpfsWithRewards.reduce( + (acc, curr) => acc + curr.matchedRewards, + 0n, + ); + const matchingFund = matchedRewards > 0n ? matchedRewards - patronsRewards : 0n; // All metrics should be visible in the same moment (design). Skeletons are visible to the end of fetching all needed data. const isLoading = @@ -113,18 +120,6 @@ const MetricsEpoch = (): ReactElement => { - - { /> + + + + - + {epoch < 4 && ( = ({ + totalUserDonationsWithPatronRewards, + isLoading, + matchingFund, + className, +}) => { + const { t } = useTranslation('translation', { keyPrefix: 'views.metrics' }); + + const totalUserDonationWithPatronRewardsNumber = parseFloat( + formatUnitsBigInt(totalUserDonationsWithPatronRewards), + ); + const matchingFundNumber = parseFloat(formatUnitsBigInt(matchingFund)); + + const donationsValue = + totalUserDonationWithPatronRewardsNumber > 0 + ? (totalUserDonationWithPatronRewardsNumber / + (matchingFundNumber + totalUserDonationWithPatronRewardsNumber)) * + 100 + : 0; + + return ( + + ), + title: t('donationsVsMatchFunding'), + }, + ]} + /> + ); +}; + +export default MetricsEpochGridDonationsVsMatching; diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridDonationsVsMatching/index.tsx b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridDonationsVsMatching/index.tsx new file mode 100644 index 0000000000..c50555aba1 --- /dev/null +++ b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridDonationsVsMatching/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './MetricsEpochGridDonationsVsMatching'; diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridDonationsVsMatching/types.ts b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridDonationsVsMatching/types.ts new file mode 100644 index 0000000000..523af1c839 --- /dev/null +++ b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridDonationsVsMatching/types.ts @@ -0,0 +1,6 @@ +export default interface MetricsEpochGridDonationsVsMatchingProps { + className?: string; + isLoading: boolean; + matchingFund: bigint; + totalUserDonationsWithPatronRewards: bigint; +} diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridDonationsVsPersonalAllocations/MetricsEpochGridDonationsVsPersonalAllocations.tsx b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridDonationsVsPersonalAllocations/MetricsEpochGridDonationsVsPersonalAllocations.tsx index 1ea059bfc3..38e18bdb75 100644 --- a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridDonationsVsPersonalAllocations/MetricsEpochGridDonationsVsPersonalAllocations.tsx +++ b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridDonationsVsPersonalAllocations/MetricsEpochGridDonationsVsPersonalAllocations.tsx @@ -31,9 +31,13 @@ const MetricsEpochGridDonationsVsPersonalAllocations: FC< groups={[ { children: ( - + ), - title: t('donationsVsPersonalAllocationValue'), + title: t('donationsVsPersonal'), }, ]} /> diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridRewardsUnused/MetricsEpochGridRewardsUnused.tsx b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridRewardsUnused/MetricsEpochGridRewardsUnused.tsx new file mode 100644 index 0000000000..f978ad07e8 --- /dev/null +++ b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridRewardsUnused/MetricsEpochGridRewardsUnused.tsx @@ -0,0 +1,44 @@ +import React, { FC } from 'react'; +import { useTranslation } from 'react-i18next'; + +import MetricsGridTile from 'components/Metrics/MetricsGrid/MetricsGridTile'; +import MetricsGridTileValue from 'components/Metrics/MetricsGrid/MetricsGridTileValue'; +import useMetricsEpoch from 'hooks/helpers/useMetrcisEpoch'; +import useEpochUnusedRewards from 'hooks/queries/useEpochUnusedRewards'; + +import MetricsEpochGridRewardsUnusedProps from './types'; + +const MetricsEpochGridRewardsUnused: FC = ({ + isLoading, + className, +}) => { + const { t } = useTranslation('translation', { keyPrefix: 'views.metrics' }); + + const { epoch } = useMetricsEpoch(); + const { data: epochUnusedRewards } = useEpochUnusedRewards(epoch); + + const users = `${epochUnusedRewards?.addresses.length || 0}`; + + return ( + + ), + title: t('rewardsUnused'), + }, + ]} + size="S" + /> + ); +}; + +export default MetricsEpochGridRewardsUnused; diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridRewardsUnused/index.tsx b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridRewardsUnused/index.tsx new file mode 100644 index 0000000000..4cc5e7d676 --- /dev/null +++ b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridRewardsUnused/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './MetricsEpochGridRewardsUnused'; diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridRewardsUnused/types.ts b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridRewardsUnused/types.ts new file mode 100644 index 0000000000..f336df0205 --- /dev/null +++ b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridRewardsUnused/types.ts @@ -0,0 +1,4 @@ +export default interface MetricsEpochGridRewardsUnusedProps { + className?: string; + isLoading: boolean; +} diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridRewardsUnusedAndUnallocatedValue/MetricsEpochGridRewardsUnusedAndUnallocatedValue.module.scss b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridRewardsUnusedAndUnallocatedValue/MetricsEpochGridRewardsUnusedAndUnallocatedValue.module.scss deleted file mode 100644 index 0e1300bc9e..0000000000 --- a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridRewardsUnusedAndUnallocatedValue/MetricsEpochGridRewardsUnusedAndUnallocatedValue.module.scss +++ /dev/null @@ -1,5 +0,0 @@ -.root { - @media #{$tablet-down} { - order: 9; - } -} diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridRewardsUnusedAndUnallocatedValue/index.tsx b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridRewardsUnusedAndUnallocatedValue/index.tsx deleted file mode 100644 index 6f1fdab2ea..0000000000 --- a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridRewardsUnusedAndUnallocatedValue/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line no-restricted-exports -export { default } from './MetricsEpochGridRewardsUnusedAndUnallocatedValue'; diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridRewardsUnusedAndUnallocatedValue/types.ts b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridRewardsUnusedAndUnallocatedValue/types.ts deleted file mode 100644 index 4f8935856e..0000000000 --- a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridRewardsUnusedAndUnallocatedValue/types.ts +++ /dev/null @@ -1,4 +0,0 @@ -export default interface MetricsEpochGridRewardsUnusedAndUnallocatedValueProps { - className?: string; - isLoading: boolean; -} diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalDonationsAndPersonal/MetricsEpochGridTotalDonationsAndPersonal.tsx b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalDonations/MetricsEpochGridTotalDonations.tsx similarity index 52% rename from client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalDonationsAndPersonal/MetricsEpochGridTotalDonationsAndPersonal.tsx rename to client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalDonations/MetricsEpochGridTotalDonations.tsx index 5c4363a6aa..d082503428 100644 --- a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalDonationsAndPersonal/MetricsEpochGridTotalDonationsAndPersonal.tsx +++ b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalDonations/MetricsEpochGridTotalDonations.tsx @@ -5,11 +5,13 @@ import MetricsGridTile from 'components/Metrics/MetricsGrid/MetricsGridTile'; import MetricsGridTileValue from 'components/Metrics/MetricsGrid/MetricsGridTileValue'; import useGetValuesToDisplay from 'hooks/helpers/useGetValuesToDisplay'; -import MetricsEpochGridTotalDonationsAndPersonalProps from './types'; +import MetricsEpochGridTotalDonationsProps from './types'; -const MetricsEpochGridTotalDonationsAndPersonal: FC< - MetricsEpochGridTotalDonationsAndPersonalProps -> = ({ isLoading, totalUserDonationsWithPatronRewards, totalPersonal, className }) => { +const MetricsEpochGridTotalDonations: FC = ({ + isLoading, + totalUserDonationsWithPatronRewards, + className, +}) => { const { t } = useTranslation('translation', { keyPrefix: 'views.metrics' }); const getValuesToDisplay = useGetValuesToDisplay(); @@ -20,21 +22,15 @@ const MetricsEpochGridTotalDonationsAndPersonal: FC< valueCrypto: totalUserDonationsWithPatronRewards, }); - const totalPersonalValues = getValuesToDisplay({ - cryptoCurrency: 'ethereum', - showCryptoSuffix: true, - valueCrypto: totalPersonal, - }); - return ( - ), - title: t('totalPersonal'), - }, ]} - size="M" + size="S" /> ); }; -export default MetricsEpochGridTotalDonationsAndPersonal; +export default MetricsEpochGridTotalDonations; diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalDonations/index.tsx b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalDonations/index.tsx new file mode 100644 index 0000000000..e13f894fcf --- /dev/null +++ b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalDonations/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './MetricsEpochGridTotalDonations'; diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalDonations/types.ts b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalDonations/types.ts new file mode 100644 index 0000000000..70faebc853 --- /dev/null +++ b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalDonations/types.ts @@ -0,0 +1,5 @@ +export default interface MetricsEpochGridTotalDonationsProps { + className?: string; + isLoading: boolean; + totalUserDonationsWithPatronRewards: bigint; +} diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalDonationsAndPersonal/MetricsEpochGridTotalDonationsAndPersonal.module.scss b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalDonationsAndPersonal/MetricsEpochGridTotalDonationsAndPersonal.module.scss deleted file mode 100644 index 79195eec67..0000000000 --- a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalDonationsAndPersonal/MetricsEpochGridTotalDonationsAndPersonal.module.scss +++ /dev/null @@ -1,5 +0,0 @@ -.root { - @media #{$tablet-down} { - order: 2; - } -} diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalDonationsAndPersonal/index.tsx b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalDonationsAndPersonal/index.tsx deleted file mode 100644 index 3d73911729..0000000000 --- a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalDonationsAndPersonal/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line no-restricted-exports -export { default } from './MetricsEpochGridTotalDonationsAndPersonal'; diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalDonationsAndPersonal/types.ts b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalDonationsAndPersonal/types.ts deleted file mode 100644 index 0b5125075c..0000000000 --- a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalDonationsAndPersonal/types.ts +++ /dev/null @@ -1,6 +0,0 @@ -export default interface MetricsEpochGridTotalDonationsAndPersonalProps { - className?: string; - isLoading: boolean; - totalPersonal: bigint; - totalUserDonationsWithPatronRewards: bigint; -} diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalMatchingFund/MetricsEpochGridTotalMatchingFund.tsx b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalMatchingFund/MetricsEpochGridTotalMatchingFund.tsx new file mode 100644 index 0000000000..026cdf4bff --- /dev/null +++ b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalMatchingFund/MetricsEpochGridTotalMatchingFund.tsx @@ -0,0 +1,47 @@ +import React, { FC } from 'react'; +import { useTranslation } from 'react-i18next'; + +import MetricsGridTile from 'components/Metrics/MetricsGrid/MetricsGridTile'; +import MetricsGridTileValue from 'components/Metrics/MetricsGrid/MetricsGridTileValue'; +import useGetValuesToDisplay from 'hooks/helpers/useGetValuesToDisplay'; + +import MetricsEpochGridTotalMatchingFundProps from './types'; + +const MetricsEpochGridTotalMatchingFund: FC = ({ + isLoading, + matchingFund, + className, +}) => { + const { t } = useTranslation('translation', { keyPrefix: 'views.metrics' }); + + const getValuesToDisplay = useGetValuesToDisplay(); + + const totalMatchingFundToDisplay = getValuesToDisplay({ + cryptoCurrency: 'ethereum', + showCryptoSuffix: true, + valueCrypto: matchingFund, + }); + + return ( + + ), + title: t('totalMatching'), + }, + ]} + size="S" + /> + ); +}; + +export default MetricsEpochGridTotalMatchingFund; diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalMatchingFund/index.tsx b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalMatchingFund/index.tsx new file mode 100644 index 0000000000..1225ad454e --- /dev/null +++ b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalMatchingFund/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './MetricsEpochGridTotalMatchingFund'; diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalMatchingFund/types.ts b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalMatchingFund/types.ts new file mode 100644 index 0000000000..75a6cf2a10 --- /dev/null +++ b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalMatchingFund/types.ts @@ -0,0 +1,5 @@ +export default interface MetricsEpochGridTotalMatchingFundProps { + className?: string; + isLoading: boolean; + matchingFund: bigint; +} diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridRewardsUnusedAndUnallocatedValue/MetricsEpochGridRewardsUnusedAndUnallocatedValue.tsx b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridUnallocatedValue/MetricsEpochGridUnallocatedValue.tsx similarity index 60% rename from client/src/components/Metrics/MetricsEpoch/MetricsEpochGridRewardsUnusedAndUnallocatedValue/MetricsEpochGridRewardsUnusedAndUnallocatedValue.tsx rename to client/src/components/Metrics/MetricsEpoch/MetricsEpochGridUnallocatedValue/MetricsEpochGridUnallocatedValue.tsx index 67e354e70d..6828b7f40b 100644 --- a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridRewardsUnusedAndUnallocatedValue/MetricsEpochGridRewardsUnusedAndUnallocatedValue.tsx +++ b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridUnallocatedValue/MetricsEpochGridUnallocatedValue.tsx @@ -7,18 +7,18 @@ import useGetValuesToDisplay from 'hooks/helpers/useGetValuesToDisplay'; import useMetricsEpoch from 'hooks/helpers/useMetrcisEpoch'; import useEpochUnusedRewards from 'hooks/queries/useEpochUnusedRewards'; -import MetricsEpochGridRewardsUnusedAndUnallocatedValueProps from './types'; +import MetricsEpochGridUnallocatedValueProps from './types'; -const MetricsEpochGridRewardsUnusedAndUnallocatedValue: FC< - MetricsEpochGridRewardsUnusedAndUnallocatedValueProps -> = ({ isLoading, className }) => { +const MetricsEpochGridUnallocatedValue: FC = ({ + isLoading, + className, +}) => { const { t } = useTranslation('translation', { keyPrefix: 'views.metrics' }); const { epoch } = useMetricsEpoch(); const { data: epochUnusedRewards } = useEpochUnusedRewards(epoch); const getValuesToDisplay = useGetValuesToDisplay(); - const users = `${epochUnusedRewards?.addresses.length || 0}`; const unallocatedValue = getValuesToDisplay({ cryptoCurrency: 'ethereum', showCryptoSuffix: true, @@ -28,23 +28,12 @@ const MetricsEpochGridRewardsUnusedAndUnallocatedValue: FC< return ( - ), - title: t('rewardsUnused'), - }, - { - children: ( - ); }; -export default MetricsEpochGridRewardsUnusedAndUnallocatedValue; +export default MetricsEpochGridUnallocatedValue; diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridUnallocatedValue/index.tsx b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridUnallocatedValue/index.tsx new file mode 100644 index 0000000000..450946b735 --- /dev/null +++ b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridUnallocatedValue/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './MetricsEpochGridUnallocatedValue'; diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridUnallocatedValue/types.ts b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridUnallocatedValue/types.ts new file mode 100644 index 0000000000..2863cb8882 --- /dev/null +++ b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridUnallocatedValue/types.ts @@ -0,0 +1,4 @@ +export default interface MetricsEpochGridUnallocatedValueProps { + className?: string; + isLoading: boolean; +} diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpochHeader/MetricsEpochHeader.tsx b/client/src/components/Metrics/MetricsEpoch/MetricsEpochHeader/MetricsEpochHeader.tsx index 0eb9ae040a..006ee3aed6 100644 --- a/client/src/components/Metrics/MetricsEpoch/MetricsEpochHeader/MetricsEpochHeader.tsx +++ b/client/src/components/Metrics/MetricsEpoch/MetricsEpochHeader/MetricsEpochHeader.tsx @@ -2,7 +2,7 @@ import cx from 'classnames'; import React, { ReactElement } from 'react'; import { useTranslation } from 'react-i18next'; -import MetricsHeader from 'components/Metrics/MetricsHeader'; +import MetricsSectionHeader from 'components/Metrics/MetricsSectionHeader'; import Svg from 'components/ui/Svg'; import useEpochDurationLabel from 'hooks/helpers/useEpochDurationLabel'; import useMediaQuery from 'hooks/helpers/useMediaQuery'; @@ -25,7 +25,9 @@ const MetricsEpochHeader = (): ReactElement => { const isLeftArrowDisabled = epoch < 2; return ( - +
{isCurrentOpenEpoch ? (
{t('open')}
@@ -57,7 +59,7 @@ const MetricsEpochHeader = (): ReactElement => {
- + ); }; diff --git a/client/src/components/Metrics/MetricsGeneral/MetricsGeneral.module.scss b/client/src/components/Metrics/MetricsGeneral/MetricsGeneral.module.scss index 5733eab620..110a552aca 100644 --- a/client/src/components/Metrics/MetricsGeneral/MetricsGeneral.module.scss +++ b/client/src/components/Metrics/MetricsGeneral/MetricsGeneral.module.scss @@ -1,5 +1,10 @@ .root { width: 100%; + padding-bottom: 4rem; + + @media #{$large-desktop-up} { + padding-bottom: 5.6rem; + } } .divider { @@ -8,3 +13,15 @@ background-color: $color-octant-grey1; margin: 3.2rem 0; } + +.grid { + grid-template-rows: repeat(6, 15.6rem); + + @media #{$tablet-up} { + grid-template-rows: repeat(3, 15.6rem); + } + + @media #{$desktop-up} { + grid-template-rows: repeat(2, 15.6rem); + } +} diff --git a/client/src/components/Metrics/MetricsGeneral/MetricsGeneral.tsx b/client/src/components/Metrics/MetricsGeneral/MetricsGeneral.tsx index 6db348e220..23328544ba 100644 --- a/client/src/components/Metrics/MetricsGeneral/MetricsGeneral.tsx +++ b/client/src/components/Metrics/MetricsGeneral/MetricsGeneral.tsx @@ -7,7 +7,7 @@ import MetricsGeneralGridTotalGlmLockedAndTotalSupply from 'components/Metrics/M import MetricsGeneralGridTotalProjects from 'components/Metrics/MetricsGeneral/MetricsGeneralGridTotalProjects'; import MetricsGeneralGridWalletsWithGlmLocked from 'components/Metrics/MetricsGeneral/MetricsGeneralGridWalletsWithGlmLocked'; import MetricsGrid from 'components/Metrics/MetricsGrid'; -import MetricsHeader from 'components/Metrics/MetricsHeader'; +import MetricsSectionHeader from 'components/Metrics/MetricsSectionHeader'; import { METRICS_GENERAL_ID } from 'constants/domElementsIds'; import useCryptoValues from 'hooks/queries/useCryptoValues'; import useProjectsEpoch from 'hooks/queries/useProjectsEpoch'; @@ -44,14 +44,13 @@ const MetricsGeneral = (): ReactElement => { return (
-
- - + + + + - -
); diff --git a/client/src/components/Metrics/MetricsGeneral/MetricsGeneralGridCumulativeGlmLocked/MetricsGeneralGridCumulativeGlmLocked.module.scss b/client/src/components/Metrics/MetricsGeneral/MetricsGeneralGridCumulativeGlmLocked/MetricsGeneralGridCumulativeGlmLocked.module.scss index 81808e7777..654d7e9d6f 100644 --- a/client/src/components/Metrics/MetricsGeneral/MetricsGeneralGridCumulativeGlmLocked/MetricsGeneralGridCumulativeGlmLocked.module.scss +++ b/client/src/components/Metrics/MetricsGeneral/MetricsGeneralGridCumulativeGlmLocked/MetricsGeneralGridCumulativeGlmLocked.module.scss @@ -1,6 +1,9 @@ .root { - @media #{$tablet-down} { - order: 2; + grid-column: span 2; + grid-row: span 2; + + @media #{$large-desktop-up} { + grid-column: span 3; } } diff --git a/client/src/components/Metrics/MetricsGeneral/MetricsGeneralGridCumulativeGlmLocked/MetricsGeneralGridCumulativeGlmLocked.tsx b/client/src/components/Metrics/MetricsGeneral/MetricsGeneralGridCumulativeGlmLocked/MetricsGeneralGridCumulativeGlmLocked.tsx index dfcbea0bf1..e3b943f57a 100644 --- a/client/src/components/Metrics/MetricsGeneral/MetricsGeneralGridCumulativeGlmLocked/MetricsGeneralGridCumulativeGlmLocked.tsx +++ b/client/src/components/Metrics/MetricsGeneral/MetricsGeneralGridCumulativeGlmLocked/MetricsGeneralGridCumulativeGlmLocked.tsx @@ -57,11 +57,12 @@ const MetricsGeneralGridCumulativeGlmLocked: FC = () => { />
), + hasTitleLargeBottomPadding: true, title: t('cumulativeGlmLocked'), titleSuffix: , }, ]} - size="L" + size="custom" /> ); }; diff --git a/client/src/components/Metrics/MetricsGeneral/MetricsGeneralGridWalletsWithGlmLocked/MetricsGeneralGridWalletsWithGlmLocked.module.scss b/client/src/components/Metrics/MetricsGeneral/MetricsGeneralGridWalletsWithGlmLocked/MetricsGeneralGridWalletsWithGlmLocked.module.scss index 5fc4f55671..654d7e9d6f 100644 --- a/client/src/components/Metrics/MetricsGeneral/MetricsGeneralGridWalletsWithGlmLocked/MetricsGeneralGridWalletsWithGlmLocked.module.scss +++ b/client/src/components/Metrics/MetricsGeneral/MetricsGeneralGridWalletsWithGlmLocked/MetricsGeneralGridWalletsWithGlmLocked.module.scss @@ -1,6 +1,9 @@ .root { - @media #{$tablet-down} { - order: 4; + grid-column: span 2; + grid-row: span 2; + + @media #{$large-desktop-up} { + grid-column: span 3; } } diff --git a/client/src/components/Metrics/MetricsGeneral/MetricsGeneralGridWalletsWithGlmLocked/MetricsGeneralGridWalletsWithGlmLocked.tsx b/client/src/components/Metrics/MetricsGeneral/MetricsGeneralGridWalletsWithGlmLocked/MetricsGeneralGridWalletsWithGlmLocked.tsx index 93d000b0cb..1475f8863f 100644 --- a/client/src/components/Metrics/MetricsGeneral/MetricsGeneralGridWalletsWithGlmLocked/MetricsGeneralGridWalletsWithGlmLocked.tsx +++ b/client/src/components/Metrics/MetricsGeneral/MetricsGeneralGridWalletsWithGlmLocked/MetricsGeneralGridWalletsWithGlmLocked.tsx @@ -47,11 +47,12 @@ const MetricsGeneralGridWalletsWithGlmLocked: FC = () => { />
), + hasTitleLargeBottomPadding: true, title: t('walletsWithGlmLocked'), titleSuffix: , }, ]} - size="L" + size="custom" /> ); }; diff --git a/client/src/components/Metrics/MetricsGrid/MetricsGridTile/MetricsGridTile.module.scss b/client/src/components/Metrics/MetricsGrid/MetricsGridTile/MetricsGridTile.module.scss index 2769c27b83..a58172da41 100644 --- a/client/src/components/Metrics/MetricsGrid/MetricsGridTile/MetricsGridTile.module.scss +++ b/client/src/components/Metrics/MetricsGrid/MetricsGridTile/MetricsGridTile.module.scss @@ -6,22 +6,21 @@ &.size { &--S { - height: 14.4rem; grid-column: span 1; grid-row: span 1; + height: 100%; } &--M { - height: 14.4rem; + height: 100%; grid-column: span 2; grid-row: span 1; } &--L { - min-height: 30.4rem; + height: 100%; grid-column: span 2; grid-row: span 2; - max-height: 50.4rem; } } } @@ -51,6 +50,10 @@ align-items: center; margin: 0; + &.hasTitleLargeBottomPadding { + padding-bottom: 3.6rem; + } + .title { text-align: left; font-size: $font-size-10; diff --git a/client/src/components/Metrics/MetricsGrid/MetricsGridTile/MetricsGridTile.tsx b/client/src/components/Metrics/MetricsGrid/MetricsGridTile/MetricsGridTile.tsx index 52c15f835e..3c25d042d3 100644 --- a/client/src/components/Metrics/MetricsGrid/MetricsGridTile/MetricsGridTile.tsx +++ b/client/src/components/Metrics/MetricsGrid/MetricsGridTile/MetricsGridTile.tsx @@ -17,7 +17,7 @@ const MetricsGridTile: FC = ({
diff --git a/client/src/components/Metrics/MetricsGrid/MetricsGridTile/types.ts b/client/src/components/Metrics/MetricsGrid/MetricsGridTile/types.ts index 69ba6c7a1d..5ebe6a9ced 100644 --- a/client/src/components/Metrics/MetricsGrid/MetricsGridTile/types.ts +++ b/client/src/components/Metrics/MetricsGrid/MetricsGridTile/types.ts @@ -5,7 +5,7 @@ export type MetricsGridTileSizes = (typeof METRICS_GRID_TILE_SZIES)[number]; type MetricsGridTileGroup = { children: ReactNode; - hasTitileBottomPadding?: boolean; + hasTitleLargeBottomPadding?: boolean; title: string; titleSuffix?: string | ReactNode; }; diff --git a/client/src/components/Metrics/MetricsGrid/MetricsGridTileTimeSlicer/MetricsGridTileTimeSlicer.module.scss b/client/src/components/Metrics/MetricsGrid/MetricsGridTileTimeSlicer/MetricsGridTileTimeSlicer.module.scss index d51b03f134..7f8bbaea88 100644 --- a/client/src/components/Metrics/MetricsGrid/MetricsGridTileTimeSlicer/MetricsGridTileTimeSlicer.module.scss +++ b/client/src/components/Metrics/MetricsGrid/MetricsGridTileTimeSlicer/MetricsGridTileTimeSlicer.module.scss @@ -8,6 +8,7 @@ padding: 0.4rem; justify-content: space-between; align-items: center; + margin-top: -0.4rem; .item { display: flex; diff --git a/client/src/components/Metrics/MetricsHeader/MetricsHeader.module.scss b/client/src/components/Metrics/MetricsHeader/MetricsHeader.module.scss deleted file mode 100644 index 9274ea8500..0000000000 --- a/client/src/components/Metrics/MetricsHeader/MetricsHeader.module.scss +++ /dev/null @@ -1,14 +0,0 @@ -.root { - height: 6.4rem; - margin: 1.6rem 0; -} - -.boxChildrenWrapper { - justify-content: left; -} - -.title { - font-size: $font-size-16; - color: $color-octant-dark; - font-weight: $font-weight-bold; -} diff --git a/client/src/components/Metrics/MetricsHeader/MetricsHeader.tsx b/client/src/components/Metrics/MetricsHeader/MetricsHeader.tsx deleted file mode 100644 index f77090f9a1..0000000000 --- a/client/src/components/Metrics/MetricsHeader/MetricsHeader.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import React, { FC } from 'react'; - -import BoxRounded from 'components/ui/BoxRounded'; - -import styles from './MetricsHeader.module.scss'; -import MetricsHeaderProps from './types'; - -const MetricsHeader: FC = ({ title, dataTest = 'MetricsHeader', children }) => { - return ( - -
{title}
- {children} -
- ); -}; - -export default MetricsHeader; diff --git a/client/src/components/Metrics/MetricsPersonal/MetricsPersonal.module.scss b/client/src/components/Metrics/MetricsPersonal/MetricsPersonal.module.scss deleted file mode 100644 index 366333883e..0000000000 --- a/client/src/components/Metrics/MetricsPersonal/MetricsPersonal.module.scss +++ /dev/null @@ -1,18 +0,0 @@ -.root { - width: 100%; -} - -.grid { - grid-template-rows: 14.4rem minmax(14.4rem, auto); -} - -.divider { - width: 100%; - height: 0.1rem; - background-color: $color-octant-grey1; - margin: 3.2rem 0; -} - -.connectWalletImage { - @include tipTileConnectWalletImage(); -} diff --git a/client/src/components/Metrics/MetricsPersonal/MetricsPersonal.tsx b/client/src/components/Metrics/MetricsPersonal/MetricsPersonal.tsx deleted file mode 100644 index 2b11eb0a89..0000000000 --- a/client/src/components/Metrics/MetricsPersonal/MetricsPersonal.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import React, { ReactElement, useState } from 'react'; -import { Trans, useTranslation } from 'react-i18next'; -import { useAccount } from 'wagmi'; - -import MetricsGrid from 'components/Metrics/MetricsGrid'; -import MetricsHeader from 'components/Metrics/MetricsHeader'; -import TipTile from 'components/shared/TipTile'; -import { METRICS_PERSONAL_ID } from 'constants/domElementsIds'; -import useMediaQuery from 'hooks/helpers/useMediaQuery'; -import useMetricsPersonalDataRewardsUsage from 'hooks/helpers/useMetricsPersonalDataRewardsUsage'; -import useTotalPatronDonations from 'hooks/helpers/useTotalPatronDonations'; -import useCryptoValues from 'hooks/queries/useCryptoValues'; -import useSettingsStore from 'store/settings/store'; - -import styles from './MetricsPersonal.module.scss'; -import MetricsPersonalGridDonationsProgressBar from './MetricsPersonalGridDonationsProgressBar'; -import MetricsPersonalGridPatronDonations from './MetricsPersonalGridPatronDonations'; -import MetricsPersonalGridTotalRewardsWithdrawals from './MetricsPersonalGridTotalRewardsWithdrawals'; - -const MetricsPersonal = (): ReactElement => { - const { isConnected } = useAccount(); - const { isDesktop } = useMediaQuery(); - const [isConnectWalletTipTileOpen, setIsConnectWalletTipTileOpen] = useState(!isConnected); - const { t } = useTranslation('translation', { keyPrefix: 'views.metrics' }); - - const { - data: { displayCurrency }, - } = useSettingsStore(({ data }) => ({ - data: { - displayCurrency: data.displayCurrency, - }, - })); - const { isFetching: isFetchingCryptoValues } = useCryptoValues(displayCurrency); - const { isFetching: isFetchingMetricsPersonalDataRewardsUsage } = - useMetricsPersonalDataRewardsUsage(); - const { data: totalPatronDonations, isFetching: isFetchingTotalPatronDonations } = - useTotalPatronDonations(); - - const isLoading = - isFetchingCryptoValues || - isFetchingMetricsPersonalDataRewardsUsage || - isFetchingTotalPatronDonations; - - const wasUserEverAPatron = totalPatronDonations && totalPatronDonations.numberOfEpochs > 0; - - return ( -
-
- - {isConnected ? ( - - - - {wasUserEverAPatron && ( - - )} - - ) : ( - setIsConnectWalletTipTileOpen(false)} - text={ - : <>]} - i18nKey="views.metrics.connectWalletTip.text" - /> - } - title={t('connectWalletTip.title')} - /> - )} -
- ); -}; - -export default MetricsPersonal; diff --git a/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridDonationsProgressBar/MetricsPersonalGridDonationsProgressBar.tsx b/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridDonationsProgressBar/MetricsPersonalGridDonationsProgressBar.tsx deleted file mode 100644 index cf4315fcb3..0000000000 --- a/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridDonationsProgressBar/MetricsPersonalGridDonationsProgressBar.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import React, { FC } from 'react'; -import { useTranslation } from 'react-i18next'; - -import MetricsDonationsProgressBar from 'components/Metrics/MetricsDonationsProgressBar'; -import MetricsGridTile from 'components/Metrics/MetricsGrid/MetricsGridTile'; -import useMetricsPersonalDataRewardsUsage from 'hooks/helpers/useMetricsPersonalDataRewardsUsage'; -import { formatUnitsBigInt } from 'utils/formatUnitsBigInt'; - -import MetricsPersonalGridDonationsProgressBarProps from './types'; - -const MetricsPersonalGridDonationsProgressBar: FC = ({ - isLoading, -}) => { - const { t } = useTranslation('translation', { keyPrefix: 'views.metrics' }); - const { data: metricsPersonalDataRewardsUsage } = useMetricsPersonalDataRewardsUsage(); - - const totalDonationsNumber = parseFloat( - formatUnitsBigInt(metricsPersonalDataRewardsUsage?.totalDonations || BigInt(0)), - ); - const totalRewardsUsedNumber = parseFloat( - formatUnitsBigInt(metricsPersonalDataRewardsUsage?.totalRewardsUsed || BigInt(0)), - ); - - const donationsValue = - totalRewardsUsedNumber > 0 ? (totalDonationsNumber / totalRewardsUsedNumber) * 100 : 0; - - return ( - - ), - title: t('donationsVsPersonalAllocationValue'), - }, - ]} - /> - ); -}; - -export default MetricsPersonalGridDonationsProgressBar; diff --git a/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridDonationsProgressBar/index.tsx b/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridDonationsProgressBar/index.tsx deleted file mode 100644 index 38b93bbd38..0000000000 --- a/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridDonationsProgressBar/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line no-restricted-exports -export { default } from './MetricsPersonalGridDonationsProgressBar'; diff --git a/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridDonationsProgressBar/types.ts b/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridDonationsProgressBar/types.ts deleted file mode 100644 index d9eb4e2fc2..0000000000 --- a/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridDonationsProgressBar/types.ts +++ /dev/null @@ -1,3 +0,0 @@ -export default interface MetricsPersonalGridDonationsProgressBarProps { - isLoading: boolean; -} diff --git a/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridPatronDonations/MetricsPersonalGridPatronDonations.tsx b/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridPatronDonations/MetricsPersonalGridPatronDonations.tsx deleted file mode 100644 index 15d76c8318..0000000000 --- a/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridPatronDonations/MetricsPersonalGridPatronDonations.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import React, { FC, memo } from 'react'; -import { useTranslation } from 'react-i18next'; - -import MetricsGridTile from 'components/Metrics/MetricsGrid/MetricsGridTile'; -import MetricsGridTileValue from 'components/Metrics/MetricsGrid/MetricsGridTileValue'; -import useGetValuesToDisplay from 'hooks/helpers/useGetValuesToDisplay'; - -import MetricsPersonalGridPatronDonationsProps from './types'; - -const MetricsPersonalGridPatronDonations: FC = ({ - isLoading, - numberOfEpochs, - value, -}) => { - const { t } = useTranslation('translation', { keyPrefix: 'views.metrics' }); - - const getValuesToDisplay = useGetValuesToDisplay(); - - const totalPatronDonationsValues = getValuesToDisplay({ - cryptoCurrency: 'ethereum', - valueCrypto: value, - }); - - return ( - - ), - title: t('patronModeActive'), - }, - { - children: ( - - ), - title: t('donatedAsPatron'), - }, - ]} - size="M" - /> - ); -}; - -export default memo(MetricsPersonalGridPatronDonations); diff --git a/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridPatronDonations/index.tsx b/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridPatronDonations/index.tsx deleted file mode 100644 index 9ad78dbe70..0000000000 --- a/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridPatronDonations/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line no-restricted-exports -export { default } from './MetricsPersonalGridPatronDonations'; diff --git a/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridPatronDonations/types.ts b/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridPatronDonations/types.ts deleted file mode 100644 index 801fc4e10f..0000000000 --- a/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridPatronDonations/types.ts +++ /dev/null @@ -1,5 +0,0 @@ -export default interface MetricsPersonalGridPatronDonationsProps { - isLoading: boolean; - numberOfEpochs: number; - value: bigint; -} diff --git a/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridTotalRewardsWithdrawals/MetricsPersonalGridTotalRewardsWithdrawals.tsx b/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridTotalRewardsWithdrawals/MetricsPersonalGridTotalRewardsWithdrawals.tsx deleted file mode 100644 index fa407d9c85..0000000000 --- a/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridTotalRewardsWithdrawals/MetricsPersonalGridTotalRewardsWithdrawals.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import React, { FC } from 'react'; -import { useTranslation } from 'react-i18next'; - -import MetricsGridTile from 'components/Metrics/MetricsGrid/MetricsGridTile'; -import MetricsGridTileValue from 'components/Metrics/MetricsGrid/MetricsGridTileValue'; -import useGetValuesToDisplay from 'hooks/helpers/useGetValuesToDisplay'; -import useMetricsPersonalDataRewardsUsage from 'hooks/helpers/useMetricsPersonalDataRewardsUsage'; - -import MetricsPersonalGridTotalRewardsWithdrawalsProps from './types'; - -const MetricsPersonalGridTotalRewardsWithdrawals: FC< - MetricsPersonalGridTotalRewardsWithdrawalsProps -> = ({ isLoading }) => { - const { t } = useTranslation('translation', { keyPrefix: 'views.metrics' }); - const { data: metricsPersonalDataRewardsUsage } = useMetricsPersonalDataRewardsUsage(); - const getValuesToDisplay = useGetValuesToDisplay(); - - const totalRewardsUsedValues = getValuesToDisplay({ - cryptoCurrency: 'ethereum', - showCryptoSuffix: true, - valueCrypto: metricsPersonalDataRewardsUsage?.totalRewardsUsed, - }); - - const totalWithdrawalsValues = getValuesToDisplay({ - cryptoCurrency: 'ethereum', - showCryptoSuffix: true, - valueCrypto: metricsPersonalDataRewardsUsage?.totalWithdrawals, - }); - - return ( - - ), - title: t('totalRewards'), - }, - { - children: ( - - ), - title: t('totalWithdrawals'), - }, - ]} - size="M" - /> - ); -}; - -export default MetricsPersonalGridTotalRewardsWithdrawals; diff --git a/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridTotalRewardsWithdrawals/index.tsx b/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridTotalRewardsWithdrawals/index.tsx deleted file mode 100644 index 99c48ddf27..0000000000 --- a/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridTotalRewardsWithdrawals/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line no-restricted-exports -export { default } from './MetricsPersonalGridTotalRewardsWithdrawals'; diff --git a/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridTotalRewardsWithdrawals/types.ts b/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridTotalRewardsWithdrawals/types.ts deleted file mode 100644 index 1913fde7b2..0000000000 --- a/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridTotalRewardsWithdrawals/types.ts +++ /dev/null @@ -1,3 +0,0 @@ -export default interface MetricsPersonalGridTotalRewardsWithdrawalsProps { - isLoading: boolean; -} diff --git a/client/src/components/Metrics/MetricsPersonal/index.tsx b/client/src/components/Metrics/MetricsPersonal/index.tsx deleted file mode 100644 index 824e21fcd5..0000000000 --- a/client/src/components/Metrics/MetricsPersonal/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line no-restricted-exports -export { default } from './MetricsPersonal'; diff --git a/client/src/components/Metrics/MetricsSectionHeader/MetricsSectionHeader.module.scss b/client/src/components/Metrics/MetricsSectionHeader/MetricsSectionHeader.module.scss new file mode 100644 index 0000000000..8e59eaa836 --- /dev/null +++ b/client/src/components/Metrics/MetricsSectionHeader/MetricsSectionHeader.module.scss @@ -0,0 +1,13 @@ +.root { + display: flex; + justify-content: space-between; + align-items: center; + height: 6.4rem; + margin: 1.6rem 0; + + .title { + font-size: $font-size-16; + color: $color-octant-dark; + font-weight: $font-weight-bold; + } +} diff --git a/client/src/components/Metrics/MetricsSectionHeader/MetricsSectionHeader.tsx b/client/src/components/Metrics/MetricsSectionHeader/MetricsSectionHeader.tsx new file mode 100644 index 0000000000..241f7e5dd2 --- /dev/null +++ b/client/src/components/Metrics/MetricsSectionHeader/MetricsSectionHeader.tsx @@ -0,0 +1,19 @@ +import React, { FC } from 'react'; + +import styles from './MetricsSectionHeader.module.scss'; +import MetricsSectionHeaderProps from './types'; + +const MetricsSectionHeader: FC = ({ + title, + dataTest = 'MetricsSectionHeader', + children, +}) => { + return ( +
+
{title}
+ {children} +
+ ); +}; + +export default MetricsSectionHeader; diff --git a/client/src/components/Metrics/MetricsHeader/index.ts b/client/src/components/Metrics/MetricsSectionHeader/index.ts similarity index 50% rename from client/src/components/Metrics/MetricsHeader/index.ts rename to client/src/components/Metrics/MetricsSectionHeader/index.ts index 989bf16060..85421ea425 100644 --- a/client/src/components/Metrics/MetricsHeader/index.ts +++ b/client/src/components/Metrics/MetricsSectionHeader/index.ts @@ -1,2 +1,2 @@ // eslint-disable-next-line no-restricted-exports -export { default } from './MetricsHeader'; +export { default } from './MetricsSectionHeader'; diff --git a/client/src/components/Metrics/MetricsHeader/types.ts b/client/src/components/Metrics/MetricsSectionHeader/types.ts similarity index 65% rename from client/src/components/Metrics/MetricsHeader/types.ts rename to client/src/components/Metrics/MetricsSectionHeader/types.ts index 9efa6993e1..061142524c 100644 --- a/client/src/components/Metrics/MetricsHeader/types.ts +++ b/client/src/components/Metrics/MetricsSectionHeader/types.ts @@ -1,6 +1,6 @@ import { ReactNode } from 'react'; -export default interface MetricsHeaderProps { +export default interface MetricsSectionHeaderProps { children?: ReactNode; dataTest?: string; title: string; diff --git a/client/src/locales/en/translation.json b/client/src/locales/en/translation.json index 14bd58f8fe..86be90a9ad 100644 --- a/client/src/locales/en/translation.json +++ b/client/src/locales/en/translation.json @@ -554,8 +554,10 @@ } }, "metrics": { + "exploreTheData": "Explore the data", "fundingLeaderboard": "Funding leaderboard", "donors": "Donors", + "personal": "Personal", "donations": "Donations", "matchFunding": "Match funding", "total": "Total", @@ -571,7 +573,10 @@ "allocationsInEth": "Allocations in ETH", "generalMetrics": "General metrics", "totalWithdrawals": "Total withdrawals", - "donationsVsPersonalAllocationValue": "Donations vs personal allocation value", + "donationsVsPersonal": "Donations vs personal", + "donationsVsMatchFunding": "Donations vs match funding", + "totalMatching": "Total matching", + "matching": "Matching", "patronModeActive": "Patron mode active", "patronModeActiveLabel": "{{numberOfEpochs}} epochs", "donatedAsPatron": "Donated as patron", diff --git a/client/src/styles/utils/_typography.scss b/client/src/styles/utils/_typography.scss index 13e5a0f704..df34cee721 100644 --- a/client/src/styles/utils/_typography.scss +++ b/client/src/styles/utils/_typography.scss @@ -1,4 +1,6 @@ $font-size-base: 10px; +$font-size-48: 4.8rem; +$font-size-40: 4rem; $font-size-32: 3.2rem; $font-size-24: 2.4rem; $font-size-20: 2rem; diff --git a/client/src/views/MetricsView/MetricsView.module.scss b/client/src/views/MetricsView/MetricsView.module.scss index 88cf0740dd..c2023686c9 100644 --- a/client/src/views/MetricsView/MetricsView.module.scss +++ b/client/src/views/MetricsView/MetricsView.module.scss @@ -1,3 +1,22 @@ -.layout { - padding-top: 10.4rem; +.header { + width: 100%; + display: flex; + align-items: center; + color: $color-octant-dark; + font-size: $font-size-24; + font-weight: $font-weight-bold; + height: 8.8rem; + + @media #{$tablet-up} { + height: 14.4rem; + font-size: $font-size-32; + } + + @media #{$desktop-up} { + font-size: $font-size-40; + } + + @media #{$large-desktop-up} { + font-size: $font-size-48; + } } diff --git a/client/src/views/MetricsView/MetricsView.tsx b/client/src/views/MetricsView/MetricsView.tsx index 3fd32135f3..12606269f3 100644 --- a/client/src/views/MetricsView/MetricsView.tsx +++ b/client/src/views/MetricsView/MetricsView.tsx @@ -1,12 +1,15 @@ import React, { ReactElement, useLayoutEffect } from 'react'; +import { useTranslation } from 'react-i18next'; import MetricsEpoch from 'components/Metrics/MetricsEpoch'; import MetricsGeneral from 'components/Metrics/MetricsGeneral/MetricsGeneral'; -import MetricsPersonal from 'components/Metrics/MetricsPersonal'; import { MetricsEpochProvider } from 'hooks/helpers/useMetrcisEpoch'; import useCurrentEpoch from 'hooks/queries/useCurrentEpoch'; +import styles from './MetricsView.module.scss'; + const MetricsView = (): ReactElement => { + const { t } = useTranslation('translation', { keyPrefix: 'views.metrics' }); const { data: currentEpoch } = useCurrentEpoch(); useLayoutEffect(() => { @@ -21,11 +24,11 @@ const MetricsView = (): ReactElement => { "It's Epoch 1, so there are no metrics for the past. It's just a placeholder, please come back in Epoch 2." ) : ( <> +
{t('exploreTheData')}
- )} From a1293ca76ddb0aef4eeefa43b9b31342257fccaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Miko=C5=82ajczyk?= Date: Thu, 19 Sep 2024 16:24:51 +0200 Subject: [PATCH 101/241] oct-1857: z-index changes --- client/src/components/shared/Layout/Layout.module.scss | 2 +- client/src/components/ui/Drawer/Drawer.module.scss | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/components/shared/Layout/Layout.module.scss b/client/src/components/shared/Layout/Layout.module.scss index 453063aebd..10222366a1 100644 --- a/client/src/components/shared/Layout/Layout.module.scss +++ b/client/src/components/shared/Layout/Layout.module.scss @@ -26,7 +26,7 @@ background: $color-octant-grey3; width: 100%; padding: 0 $layout-horizontal-padding-small; - z-index: $z-index-8; + z-index: $z-index-5; backdrop-filter: blur(2.5rem); &.isPatronMode { diff --git a/client/src/components/ui/Drawer/Drawer.module.scss b/client/src/components/ui/Drawer/Drawer.module.scss index 30ce9b42ab..279abf4399 100644 --- a/client/src/components/ui/Drawer/Drawer.module.scss +++ b/client/src/components/ui/Drawer/Drawer.module.scss @@ -6,7 +6,7 @@ right: 0; background: $color-octant-grey6; top: 0; - z-index: $z-index-8; + z-index: $z-index-5; box-shadow: 0 0 5rem 0 $color-black-08; .buttonClose { From e1d24d58ce26872bb0c3b547cc85ef8d8d6096d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Miko=C5=82ajczyk?= Date: Thu, 19 Sep 2024 16:32:30 +0200 Subject: [PATCH 102/241] oct-1962: TransactionsListItem key fix --- .../TransactionsList/TransactionsList.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/client/src/components/Home/HomeGridTransactions/TransactionsList/TransactionsList.tsx b/client/src/components/Home/HomeGridTransactions/TransactionsList/TransactionsList.tsx index dd8f47e6d7..bab04d61ed 100644 --- a/client/src/components/Home/HomeGridTransactions/TransactionsList/TransactionsList.tsx +++ b/client/src/components/Home/HomeGridTransactions/TransactionsList/TransactionsList.tsx @@ -17,9 +17,8 @@ const TransactionsList: FC = ({ history }) => { return ( - {history.map((element, index) => ( - // eslint-disable-next-line react/no-array-index-key - + {history.map(element => ( + ))} ); From faacff2b26d9f246e3c214d72f1c0a77dc743c6d Mon Sep 17 00:00:00 2001 From: Housekeeper Bot Date: Fri, 20 Sep 2024 07:37:48 +0000 Subject: [PATCH 103/241] [CI/CD] Update uat.env contracts --- ci/argocd/contracts/uat.env | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/ci/argocd/contracts/uat.env b/ci/argocd/contracts/uat.env index bebd723cfa..e08a716201 100644 --- a/ci/argocd/contracts/uat.env +++ b/ci/argocd/contracts/uat.env @@ -1,8 +1,8 @@ -BLOCK_NUMBER=6683543 +BLOCK_NUMBER=6725985 GLM_CONTRACT_ADDRESS=0x71432DD1ae7DB41706ee6a22148446087BdD0906 -AUTH_CONTRACT_ADDRESS=0x8bCbFdc1e53a2fC55b64ab17b494b7Bc06100323 -DEPOSITS_CONTRACT_ADDRESS=0x6fBcF5081Fa8796f17d5a3598452693e4cf9B3a3 -EPOCHS_CONTRACT_ADDRESS=0x3CCBBa4359874ff0d0Bd4C494A556f2aff3470fd -PROPOSALS_CONTRACT_ADDRESS=0x1CaE72eFe2f769F85B88e5229c13DE12e86aE714 -WITHDRAWALS_TARGET_CONTRACT_ADDRESS=0x3b71Ff1b760F0998A85a79864a06F6254CB7FFBd -VAULT_CONTRACT_ADDRESS=0x690B3c3D9ea25524AAE2Dc85f04B189C1c2DfF5b +AUTH_CONTRACT_ADDRESS=0x8aa3e15BBA708d46B5e3Bd786A899b054260ECDC +DEPOSITS_CONTRACT_ADDRESS=0x0db12DE3E3f6887A4308BBC81b873f0D3c08D62F +EPOCHS_CONTRACT_ADDRESS=0xfec3fCa4bE643d961dCd327C411d90DF4E0F12B8 +PROPOSALS_CONTRACT_ADDRESS=0xB41D5fc5D9059127e5256Bbb77Df9d25d0373372 +WITHDRAWALS_TARGET_CONTRACT_ADDRESS=0x881D5A2003e8ba59189e50eBe1E9dEf1F8d1C469 +VAULT_CONTRACT_ADDRESS=0x821Dfe7AE82d2cEC385c00D5F785c74f5434cE3A From 207613ef800b4766b79962538aa3b638146b8429 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Zi=C3=B3=C5=82ek?= Date: Fri, 20 Sep 2024 13:03:05 +0200 Subject: [PATCH 104/241] fix: i18n key --- client/src/views/ProjectsView/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/views/ProjectsView/utils.ts b/client/src/views/ProjectsView/utils.ts index 8bb61032e0..1e3e4d37ae 100644 --- a/client/src/views/ProjectsView/utils.ts +++ b/client/src/views/ProjectsView/utils.ts @@ -4,7 +4,7 @@ import { Option } from 'components/ui/InputSelect/types'; export const ORDER_OPTIONS = (t: TFunction): Option[] => [ { - label: t('sortOptions.randomised'), + label: t('sortOptions.randomized'), value: 'randomized', }, { From c15726415aa01e978a4ea20ddb8a449b5935e05a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Miko=C5=82ajczyk?= Date: Fri, 20 Sep 2024 15:44:57 +0200 Subject: [PATCH 105/241] oct-1857: randomized projects fix --- .../Projects/ProjectsList/ProjectsList.tsx | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/client/src/components/Projects/ProjectsList/ProjectsList.tsx b/client/src/components/Projects/ProjectsList/ProjectsList.tsx index ae9615f4b7..028b651ce9 100644 --- a/client/src/components/Projects/ProjectsList/ProjectsList.tsx +++ b/client/src/components/Projects/ProjectsList/ProjectsList.tsx @@ -8,7 +8,9 @@ import Grid from 'components/shared/Grid'; import { PROJECTS_ADDRESSES_RANDOMIZED_ORDER } from 'constants/localStorageKeys'; import useEpochDurationLabel from 'hooks/helpers/useEpochDurationLabel'; import useProjectsEpoch from 'hooks/queries/useProjectsEpoch'; -import useProjectsIpfsWithRewards from 'hooks/queries/useProjectsIpfsWithRewards'; +import useProjectsIpfsWithRewards, { + ProjectIpfsWithRewards, +} from 'hooks/queries/useProjectsIpfsWithRewards'; import { ProjectsAddressesRandomizedOrder } from 'types/localStorage'; import styles from './ProjectsList.module.scss'; @@ -31,13 +33,21 @@ const ProjectsList: FC = ({ const isLoading = isFetchingProjectsEpoch || isFetchingProjectsWithRewards; - const projectsIpfsWithRewardsSorted = useMemo(() => { + const projectsIpfsWithRewardsSorted = useMemo((): ProjectIpfsWithRewards[] => { switch (orderOption) { case 'randomized': { const projectsAddressesRandomizedOrder = JSON.parse( localStorage.getItem(PROJECTS_ADDRESSES_RANDOMIZED_ORDER)!, ) as ProjectsAddressesRandomizedOrder; - return projectsAddressesRandomizedOrder.addressesRandomizedOrder; + + const { addressesRandomizedOrder } = projectsAddressesRandomizedOrder; + + return projectsIpfsWithRewards.sort((a, b) => { + return ( + addressesRandomizedOrder.indexOf(a.address) - + addressesRandomizedOrder.indexOf(b.address) + ); + }); } case 'alphabeticalAscending': { const projectIpfsWithRewardsFiltered = projectsIpfsWithRewards.filter( From 3a6a97ff036225e9deb2693a6f5cd83e9e0443f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Miko=C5=82ajczyk?= Date: Fri, 20 Sep 2024 16:47:47 +0200 Subject: [PATCH 106/241] oct-1857: prevent fetching userAllocations when AW is open --- client/src/components/Allocation/Allocation.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/client/src/components/Allocation/Allocation.tsx b/client/src/components/Allocation/Allocation.tsx index 9af4baf002..8b53b25527 100644 --- a/client/src/components/Allocation/Allocation.tsx +++ b/client/src/components/Allocation/Allocation.tsx @@ -103,6 +103,9 @@ const Allocation = (): ReactElement => { const [showLowUQScoreModal, setShowLowUQScoreModal] = useState(false); const { refetch: refetchEpochAllocations } = useEpochAllocations( isDecisionWindowOpen ? currentEpoch! - 1 : currentEpoch!, + { + enabled: isDecisionWindowOpen === true, + }, ); const { isDesktop } = useMediaQuery(); From ea38f6dee22914485243780a7add6aaaac7781ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Miko=C5=82ajczyk?= Date: Fri, 20 Sep 2024 16:50:35 +0200 Subject: [PATCH 107/241] oct-1857: open/close allocation drawer by clicking edit button on home donations tile --- .../components/Home/HomeGridDonations/HomeGridDonations.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/client/src/components/Home/HomeGridDonations/HomeGridDonations.tsx b/client/src/components/Home/HomeGridDonations/HomeGridDonations.tsx index 3c03907674..3391160498 100644 --- a/client/src/components/Home/HomeGridDonations/HomeGridDonations.tsx +++ b/client/src/components/Home/HomeGridDonations/HomeGridDonations.tsx @@ -29,7 +29,8 @@ const HomeGridDonations: FC = ({ className }) => { const { data: userAllocations, isFetching: isFetchingUserAllocations } = useUserAllocations(); const { data: currentEpoch } = useCurrentEpoch(); const { data: isDecisionWindowOpen } = useIsDecisionWindowOpen(); - const { setIsAllocationDrawerOpen } = useLayoutStore(state => ({ + const { isAllocationDrawerOpen, setIsAllocationDrawerOpen } = useLayoutStore(state => ({ + isAllocationDrawerOpen: state.data.isAllocationDrawerOpen, setIsAllocationDrawerOpen: state.setIsAllocationDrawerOpen, })); @@ -62,7 +63,7 @@ const HomeGridDonations: FC = ({ className }) => { className={styles.editButton} onClick={() => { setCurrentView('edit'); - setIsAllocationDrawerOpen(true); + setIsAllocationDrawerOpen(!isAllocationDrawerOpen); }} variant="cta" > From 61a3a4fe94407fec392fbe2db52c677aa35f17d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Miko=C5=82ajczyk?= Date: Fri, 20 Sep 2024 17:25:38 +0200 Subject: [PATCH 108/241] oct-1857: prevent fetching userAllocations and currentEpoch each time --- client/src/hooks/queries/useCurrentEpoch.ts | 1 + client/src/hooks/queries/useUserAllocations.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/client/src/hooks/queries/useCurrentEpoch.ts b/client/src/hooks/queries/useCurrentEpoch.ts index c7ca5cfef2..8dd7e6f6db 100644 --- a/client/src/hooks/queries/useCurrentEpoch.ts +++ b/client/src/hooks/queries/useCurrentEpoch.ts @@ -18,6 +18,7 @@ export default function useCurrentEpoch( }), queryKey: QUERY_KEYS.currentEpoch, select: res => Number(res), + staleTime: Infinity, ...options, }); } diff --git a/client/src/hooks/queries/useUserAllocations.ts b/client/src/hooks/queries/useUserAllocations.ts index 65adfdb11c..0d354ab2cd 100644 --- a/client/src/hooks/queries/useUserAllocations.ts +++ b/client/src/hooks/queries/useUserAllocations.ts @@ -43,6 +43,7 @@ export default function useUserAllocations( isManuallyEdited: !!response.isManuallyEdited, }; }, + staleTime: Infinity, ...options, }); } From 0a8ce269ded557a67c12c72e501a6097f646b6b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Miko=C5=82ajczyk?= Date: Sat, 21 Sep 2024 00:05:23 +0200 Subject: [PATCH 109/241] oct-1931: epoch results graph --- .../Allocation/AllocationItem/types.ts | 7 +- .../EpochResults/EpochResults.tsx | 65 +++++++++---------- .../EpochResultsBar.module.scss | 2 +- .../EpochResultsBar/EpochResultsBar.tsx | 12 +--- .../EpochResultsBar/types.ts | 1 + .../EpochResultsDetails.tsx | 18 +++-- .../EpochResultsDetails/types.ts | 4 +- .../HomeGridEpochResults.tsx | 2 +- .../MetricsProjectsList.tsx | 6 +- .../MetricsProjectsListItem.tsx | 9 +-- .../Metrics/MetricsProjectsListItem/types.ts | 4 +- .../queries/useProjectsIpfsWithRewards.ts | 21 +++++- client/src/mocks/subgraph/projects.ts | 1 + 13 files changed, 83 insertions(+), 69 deletions(-) diff --git a/client/src/components/Allocation/AllocationItem/types.ts b/client/src/components/Allocation/AllocationItem/types.ts index 11119a94e8..5811ffcc8e 100644 --- a/client/src/components/Allocation/AllocationItem/types.ts +++ b/client/src/components/Allocation/AllocationItem/types.ts @@ -12,7 +12,12 @@ export interface AllocationItemWithAllocations extends ProjectIpfsWithRewards { export default interface AllocationItemProps extends Omit< AllocationItemWithAllocations, - 'totalValueOfAllocations' | 'percentage' | 'numberOfDonors' | 'matchedRewards' | 'donations' + | 'totalValueOfAllocations' + | 'percentage' + | 'numberOfDonors' + | 'matchedRewards' + | 'donations' + | 'matchingFund' > { className?: string; isError: boolean; diff --git a/client/src/components/Home/HomeGridEpochResults/EpochResults/EpochResults.tsx b/client/src/components/Home/HomeGridEpochResults/EpochResults/EpochResults.tsx index 9bc27880bd..8e522ab83c 100644 --- a/client/src/components/Home/HomeGridEpochResults/EpochResults/EpochResults.tsx +++ b/client/src/components/Home/HomeGridEpochResults/EpochResults/EpochResults.tsx @@ -1,51 +1,45 @@ import { maxBy } from 'lodash'; -import React, { ReactNode, useEffect, useMemo, useState } from 'react'; +import React, { FC, useEffect, useState } from 'react'; import EpochResultsBar from 'components/Home/HomeGridEpochResults/EpochResultsBar'; import EpochResultsDetails from 'components/Home/HomeGridEpochResults/EpochResultsDetails'; import { EPOCH_RESULTS_BAR_ID } from 'constants/domElementsIds'; +import env from 'env'; +import useProjectsIpfsWithRewards, { + ProjectIpfsWithRewards, +} from 'hooks/queries/useProjectsIpfsWithRewards'; import styles from './EpochResults.module.scss'; -// TODO: replace with real data -> https://linear.app/golemfoundation/issue/OCT-1931/home-epoch-results-live-connect-with-be -const data = [ - { - address: '0x576edCed7475D8F64a5e2D5227c93Ca57d7f5d20', - donations: 10n, - matching: 100n, - }, - { - address: '0x53390590476dC98860316e4B46Bb9842AF55efc4', - donations: 15n, - matching: 250n, - }, - { - address: '0x9531C059098e3d194fF87FebB587aB07B30B1306', - donations: 40n, - matching: 100n, - }, -]; - -const EpochResults = (): ReactNode => { +const EpochResults: FC<{ epoch: number }> = ({ epoch }) => { + const { data: projectsIpfsWithRewards } = useProjectsIpfsWithRewards(); const [highlightedBarAddress, setHighlightedBarAddress] = useState(null); + const { ipfsGateways } = env; - const details = useMemo(() => { - const projectData = data.find(d => d.address === highlightedBarAddress); - return projectData; - }, [highlightedBarAddress]); + const projects = + projectsIpfsWithRewards.map(props => ({ + epoch, + ...props, + })) || []; + + const details = projects.find(d => d.address === highlightedBarAddress); const getMaxValue = (): bigint => { - const { matching, donations } = maxBy(data, d => { - if (d.donations > d.matching) { + const { matchingFund, donations } = maxBy(projects, d => { + if (d.donations > d.matchingFund) { return d.donations; } - return d.matching; - }) as { donations: bigint; matching: bigint }; - return matching > donations ? matching : donations; + return d.matchingFund; + }) as ProjectIpfsWithRewards; + return matchingFund > donations ? matchingFund : donations; }; const getBarHeightPercentage = (value: bigint) => { - return (Number(value) / Number(getMaxValue())) * 100; + const maxValue = getMaxValue(); + if (!maxValue || !value) { + return 0; + } + return (Number(value) / Number(maxValue)) * 100; }; useEffect(() => { @@ -72,19 +66,20 @@ const EpochResults = (): ReactNode => { return (
- {data.map(({ address, matching, donations }) => ( + {projects.map(({ address, matchingFund, donations, profileImageSmall }) => ( `${element}${profileImageSmall}`)} isHighlighted={!!(highlightedBarAddress && highlightedBarAddress === address)} isLowlighted={!!(highlightedBarAddress && highlightedBarAddress !== address)} onClick={setHighlightedBarAddress} - topBarHeightPercentage={getBarHeightPercentage(matching)} + topBarHeightPercentage={getBarHeightPercentage(matchingFund)} /> ))}
- +
); }; diff --git a/client/src/components/Home/HomeGridEpochResults/EpochResultsBar/EpochResultsBar.module.scss b/client/src/components/Home/HomeGridEpochResults/EpochResultsBar/EpochResultsBar.module.scss index 06affe4b4f..6c4d33f1cf 100644 --- a/client/src/components/Home/HomeGridEpochResults/EpochResultsBar/EpochResultsBar.module.scss +++ b/client/src/components/Home/HomeGridEpochResults/EpochResultsBar/EpochResultsBar.module.scss @@ -50,7 +50,7 @@ border-radius: 100%; background-color: $color-white; box-shadow: 0 0 2.5rem 0 $color-black-10; - z-index: $z-index-2; + z-index: $z-index-3; .projectLogoImg { height: 2.4rem; diff --git a/client/src/components/Home/HomeGridEpochResults/EpochResultsBar/EpochResultsBar.tsx b/client/src/components/Home/HomeGridEpochResults/EpochResultsBar/EpochResultsBar.tsx index 0c2ed597ca..65d25d5f11 100644 --- a/client/src/components/Home/HomeGridEpochResults/EpochResultsBar/EpochResultsBar.tsx +++ b/client/src/components/Home/HomeGridEpochResults/EpochResultsBar/EpochResultsBar.tsx @@ -3,8 +3,6 @@ import React, { FC, useEffect, useRef, useState } from 'react'; import Img from 'components/ui/Img'; import { EPOCH_RESULTS_BAR_ID } from 'constants/domElementsIds'; -import env from 'env'; -import useProjectsIpfs from 'hooks/queries/useProjectsIpfs'; import styles from './EpochResultsBar.module.scss'; import EpochResultsBarProps from './types'; @@ -16,8 +14,8 @@ const EpochResultsBar: FC = ({ onClick, isHighlighted, isLowlighted, + imageSources, }) => { - const { ipfsGateways } = env; const topBarRef = useRef(null); const bottomBarRef = useRef(null); const ref = useRef(null); @@ -26,12 +24,6 @@ const EpochResultsBar: FC = ({ const [isProjectLogoVisible, setIsProjectLogoVisible] = useState(false); - const { data: projectIpfs } = useProjectsIpfs([address]); - - const projectLogoImageSources = ipfsGateways - .split(',') - .map(element => `${element}${projectIpfs[0]?.profileImageSmall}`); - useEffect(() => { if (!isInView) { return; @@ -83,7 +75,7 @@ const EpochResultsBar: FC = ({ x: '50%', }} > - +
)} diff --git a/client/src/components/Home/HomeGridEpochResults/EpochResultsBar/types.ts b/client/src/components/Home/HomeGridEpochResults/EpochResultsBar/types.ts index aca66ebc28..dc55ec5465 100644 --- a/client/src/components/Home/HomeGridEpochResults/EpochResultsBar/types.ts +++ b/client/src/components/Home/HomeGridEpochResults/EpochResultsBar/types.ts @@ -1,6 +1,7 @@ export default interface EpochResultsBarProps { address: string; bottomBarHeightPercentage: number; + imageSources: string[]; isHighlighted: boolean; isLowlighted: boolean; onClick: (address: string) => void; diff --git a/client/src/components/Home/HomeGridEpochResults/EpochResultsDetails/EpochResultsDetails.tsx b/client/src/components/Home/HomeGridEpochResults/EpochResultsDetails/EpochResultsDetails.tsx index cea7b20dcf..eb0994cd2e 100644 --- a/client/src/components/Home/HomeGridEpochResults/EpochResultsDetails/EpochResultsDetails.tsx +++ b/client/src/components/Home/HomeGridEpochResults/EpochResultsDetails/EpochResultsDetails.tsx @@ -1,10 +1,11 @@ import React, { FC } from 'react'; import { useTranslation } from 'react-i18next'; +import { useNavigate } from 'react-router-dom'; import Button from 'components/ui/Button'; import useGetValuesToDisplay from 'hooks/helpers/useGetValuesToDisplay'; import useMediaQuery from 'hooks/helpers/useMediaQuery'; -import useProjectsIpfs from 'hooks/queries/useProjectsIpfs'; +import { ROOT_ROUTES } from 'routes/RootRoutes/routes'; import styles from './EpochResultsDetails.module.scss'; import EpochResultsDetailsProps from './types'; @@ -14,10 +15,9 @@ const EpochResultsDetails: FC = ({ details }) => { keyPrefix: 'components.home.homeGridEpochResults', }); const { isMobile } = useMediaQuery(); + const navigate = useNavigate(); const getValuesToDisplay = useGetValuesToDisplay(); - const { data: projectsIpfs } = useProjectsIpfs([details?.address], 5, details !== null); - const donationsToDisplay = details ? getValuesToDisplay({ cryptoCurrency: 'ethereum', @@ -38,7 +38,7 @@ const EpochResultsDetails: FC = ({ details }) => { ? getValuesToDisplay({ cryptoCurrency: 'ethereum', showCryptoSuffix: true, - valueCrypto: details.donations + details.matching, + valueCrypto: details.donations + details.matchingFund, }).primary : null; @@ -46,7 +46,7 @@ const EpochResultsDetails: FC = ({ details }) => {
{details ? ( <> -
{projectsIpfs?.[0]?.name}
+
{details.name}
{isMobile ? t('donationsShort') : t('donations')} {donationsToDisplay}
@@ -57,7 +57,13 @@ const EpochResultsDetails: FC = ({ details }) => { {isMobile ? t('totalShort') : t('total')} {totalToDisplay}
{!isMobile && ( - )} diff --git a/client/src/components/Home/HomeGridEpochResults/EpochResultsDetails/types.ts b/client/src/components/Home/HomeGridEpochResults/EpochResultsDetails/types.ts index 3cb78de65e..7b49c2abf6 100644 --- a/client/src/components/Home/HomeGridEpochResults/EpochResultsDetails/types.ts +++ b/client/src/components/Home/HomeGridEpochResults/EpochResultsDetails/types.ts @@ -1,3 +1,5 @@ +import { ProjectIpfsWithRewards } from 'hooks/queries/useProjectsIpfsWithRewards'; + export default interface EpochResultsDetailsProps { - details?: any; + details?: ProjectIpfsWithRewards & { epoch: number }; } diff --git a/client/src/components/Home/HomeGridEpochResults/HomeGridEpochResults.tsx b/client/src/components/Home/HomeGridEpochResults/HomeGridEpochResults.tsx index 954c1c92d7..df0d11df32 100644 --- a/client/src/components/Home/HomeGridEpochResults/HomeGridEpochResults.tsx +++ b/client/src/components/Home/HomeGridEpochResults/HomeGridEpochResults.tsx @@ -67,7 +67,7 @@ const HomeGridEpochResults: FC = ({ className }) => { } >
- +
); diff --git a/client/src/components/Metrics/MetricsProjectsList/MetricsProjectsList.tsx b/client/src/components/Metrics/MetricsProjectsList/MetricsProjectsList.tsx index 8664d99f50..de406b0ccc 100644 --- a/client/src/components/Metrics/MetricsProjectsList/MetricsProjectsList.tsx +++ b/client/src/components/Metrics/MetricsProjectsList/MetricsProjectsList.tsx @@ -26,7 +26,6 @@ const MetricsProjectsList: FC = ({ : projects.map(project => ( = ({ valueCrypto: project.donations, }).primary } - epoch={project.epoch} + image={project.profileImageSmall!} matchFunding={ getValuesToDisplay({ cryptoCurrency: 'ethereum', @@ -49,9 +48,10 @@ const MetricsProjectsList: FC = ({ shouldIgnoreWei: true, }, showCryptoSuffix: true, - valueCrypto: project.matchedRewards, + valueCrypto: project.matchingFund, }).primary } + name={project.name!} numberOfDonors={project.numberOfDonors} total={ getValuesToDisplay({ diff --git a/client/src/components/Metrics/MetricsProjectsListItem/MetricsProjectsListItem.tsx b/client/src/components/Metrics/MetricsProjectsListItem/MetricsProjectsListItem.tsx index 5b1dd7e38d..39e130c0ee 100644 --- a/client/src/components/Metrics/MetricsProjectsListItem/MetricsProjectsListItem.tsx +++ b/client/src/components/Metrics/MetricsProjectsListItem/MetricsProjectsListItem.tsx @@ -4,27 +4,22 @@ import React, { FC, memo } from 'react'; import Img from 'components/ui/Img/Img'; import env from 'env'; import useMediaQuery from 'hooks/helpers/useMediaQuery'; -import useProjectsIpfs from 'hooks/queries/useProjectsIpfs'; import styles from './MetricsProjectsListItem.module.scss'; import MetricsProjectsListItemProps from './types'; const MetricsProjectsListItem: FC = ({ - address, - epoch, numberOfDonors, donations, matchFunding, total, dataTest = 'MetricsProjectsListItem', + image, + name, }) => { const { ipfsGateways } = env; - const { data: projectsIpfs } = useProjectsIpfs([address], epoch); const { isLargeDesktop } = useMediaQuery(); - const image = projectsIpfs.at(0)?.profileImageSmall; - const name = projectsIpfs.at(0)?.name; - return (
diff --git a/client/src/components/Metrics/MetricsProjectsListItem/types.ts b/client/src/components/Metrics/MetricsProjectsListItem/types.ts index 251024fdb1..e84aec2ab1 100644 --- a/client/src/components/Metrics/MetricsProjectsListItem/types.ts +++ b/client/src/components/Metrics/MetricsProjectsListItem/types.ts @@ -1,9 +1,9 @@ export default interface MetricsProjectsListItemProps { - address: string; dataTest?: string; donations: string; - epoch?: number; + image: string; matchFunding: string; + name: string; numberOfDonors: number; total: string; } diff --git a/client/src/hooks/queries/useProjectsIpfsWithRewards.ts b/client/src/hooks/queries/useProjectsIpfsWithRewards.ts index 32ca541d5e..ee00b8802d 100644 --- a/client/src/hooks/queries/useProjectsIpfsWithRewards.ts +++ b/client/src/hooks/queries/useProjectsIpfsWithRewards.ts @@ -2,6 +2,9 @@ import { ExtendedProject } from 'types/extended-project'; import getSortedElementsByTotalValueOfAllocationsAndAlphabetical from 'utils/getSortedElementsByTotalValueOfAllocationsAndAlphabetical'; import useProjectsDonors from './donors/useProjectsDonors'; +import useCurrentEpoch from './useCurrentEpoch'; +import useEpochInfo from './useEpochInfo'; +import useIsDecisionWindowOpen from './useIsDecisionWindowOpen'; import useMatchedProjectRewards from './useMatchedProjectRewards'; import useProjectsEpoch from './useProjectsEpoch'; import useProjectsIpfs from './useProjectsIpfs'; @@ -10,6 +13,7 @@ export interface ProjectIpfsWithRewards extends ExtendedProject { address: string; donations: bigint; matchedRewards: bigint; + matchingFund: bigint; numberOfDonors: number; percentage: number | undefined; totalValueOfAllocations: bigint; @@ -20,6 +24,8 @@ export default function useProjectsIpfsWithRewards(epoch?: number): { isAnyIpfsError: boolean; isFetching: boolean; } { + const { data: currentEpoch } = useCurrentEpoch(); + const { data: isDecisionWindowOpen } = useIsDecisionWindowOpen(); const { data: projectsAddresses, isFetching: isFetchingProjectsContract } = useProjectsEpoch(epoch); const { @@ -39,11 +45,16 @@ export default function useProjectsIpfsWithRewards(epoch?: number): { isSuccess: isSuccessProjectsDonors, } = useProjectsDonors(epoch); + const { data: epochInfo, isFetching: isFetchingEpochInfo } = useEpochInfo( + epoch ?? (isDecisionWindowOpen ? currentEpoch! - 1 : currentEpoch!), + ); + const isFetching = isFetchingProjectsContract || isFetchingProjectsIpfs || (isFetchingMatchedProjectRewards && !isRefetchingMatchedProjectRewards) || - isFetchingProjectsDonors; + isFetchingProjectsDonors || + isFetchingEpochInfo; if (isFetching) { return { data: [], @@ -52,10 +63,15 @@ export default function useProjectsIpfsWithRewards(epoch?: number): { }; } + const patronsRewards = epochInfo?.patronsRewards || BigInt(0); + const projectsWithRewards = (projectsIpfs || []).map(project => { const projectMatchedProjectRewards = matchedProjectRewards?.find( ({ address }) => address === project.address, ); + + const matchedRewards = projectMatchedProjectRewards?.matched || 0n; + /** * For epochs finalized projectMatchedProjectRewards contains data only for projects that * passed threshold. For those that did not, we reduce on their donors and get the value. @@ -67,7 +83,8 @@ export default function useProjectsIpfsWithRewards(epoch?: number): { return { donations, - matchedRewards: projectMatchedProjectRewards?.matched || 0n, + matchedRewards, + matchingFund: matchedRewards - patronsRewards, numberOfDonors: isSuccessProjectsDonors ? projectsDonors[project.address]?.length || 0 : 0, percentage: projectMatchedProjectRewards?.percentage, totalValueOfAllocations: donations + (projectMatchedProjectRewards?.matched ?? 0n), diff --git a/client/src/mocks/subgraph/projects.ts b/client/src/mocks/subgraph/projects.ts index e921042e5a..23e50654cd 100644 --- a/client/src/mocks/subgraph/projects.ts +++ b/client/src/mocks/subgraph/projects.ts @@ -9,6 +9,7 @@ export const mockedProjectATotalValueOfAllocations1: ProjectIpfsWithRewards = { numberOfDonors: 10, percentage: 1, totalValueOfAllocations: BigInt(1), + matchingFund: BigInt(1), }; export const mockedProjectATotalValueOfAllocationsUndefined = { From 6393aa22089b6104e685f8963a84e4afbfacabad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Miko=C5=82ajczyk?= Date: Sat, 21 Sep 2024 00:05:39 +0200 Subject: [PATCH 110/241] oct-1931: subgraph mock update --- client/src/mocks/subgraph/projects.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/mocks/subgraph/projects.ts b/client/src/mocks/subgraph/projects.ts index 23e50654cd..b7e5a15a0f 100644 --- a/client/src/mocks/subgraph/projects.ts +++ b/client/src/mocks/subgraph/projects.ts @@ -5,11 +5,11 @@ export const mockedProjectATotalValueOfAllocations1: ProjectIpfsWithRewards = { donations: BigInt(1), isLoadingError: false, matchedRewards: BigInt(1), + matchingFund: BigInt(1), name: 'A', numberOfDonors: 10, percentage: 1, totalValueOfAllocations: BigInt(1), - matchingFund: BigInt(1), }; export const mockedProjectATotalValueOfAllocationsUndefined = { From 96d1f84a92fb390ad7da67a42b40c9b6cb76e4a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Miko=C5=82ajczyk?= Date: Mon, 23 Sep 2024 11:26:18 +0200 Subject: [PATCH 111/241] oct-1931: matching fund as a sum of octant and patrons donations --- .../EpochResults/EpochResults.tsx | 12 +++++------ .../EpochResultsDetails.tsx | 4 ++-- .../Metrics/MetricsEpoch/MetricsEpoch.tsx | 11 +++++----- .../MetricsEpochGridDonationsVsMatching.tsx | 12 ++++------- .../types.ts | 2 +- ...pochGridDonationsVsPersonalAllocations.tsx | 12 ++++------- .../types.ts | 2 +- .../MetricsEpochGridTotalDonations.tsx | 10 +++++----- .../MetricsEpochGridTotalDonations/types.ts | 2 +- .../MetricsProjectsList.tsx | 2 +- .../queries/useProjectsIpfsWithRewards.ts | 20 +++---------------- client/src/mocks/subgraph/projects.ts | 1 - 12 files changed, 33 insertions(+), 57 deletions(-) diff --git a/client/src/components/Home/HomeGridEpochResults/EpochResults/EpochResults.tsx b/client/src/components/Home/HomeGridEpochResults/EpochResults/EpochResults.tsx index 8e522ab83c..cc41147a86 100644 --- a/client/src/components/Home/HomeGridEpochResults/EpochResults/EpochResults.tsx +++ b/client/src/components/Home/HomeGridEpochResults/EpochResults/EpochResults.tsx @@ -25,13 +25,13 @@ const EpochResults: FC<{ epoch: number }> = ({ epoch }) => { const details = projects.find(d => d.address === highlightedBarAddress); const getMaxValue = (): bigint => { - const { matchingFund, donations } = maxBy(projects, d => { - if (d.donations > d.matchingFund) { + const { matchedRewards, donations } = maxBy(projects, d => { + if (d.donations > d.matchedRewards) { return d.donations; } - return d.matchingFund; + return d.matchedRewards; }) as ProjectIpfsWithRewards; - return matchingFund > donations ? matchingFund : donations; + return matchedRewards > donations ? matchedRewards : donations; }; const getBarHeightPercentage = (value: bigint) => { @@ -66,7 +66,7 @@ const EpochResults: FC<{ epoch: number }> = ({ epoch }) => { return (
- {projects.map(({ address, matchingFund, donations, profileImageSmall }) => ( + {projects.map(({ address, matchedRewards, donations, profileImageSmall }) => ( = ({ epoch }) => { isHighlighted={!!(highlightedBarAddress && highlightedBarAddress === address)} isLowlighted={!!(highlightedBarAddress && highlightedBarAddress !== address)} onClick={setHighlightedBarAddress} - topBarHeightPercentage={getBarHeightPercentage(matchingFund)} + topBarHeightPercentage={getBarHeightPercentage(matchedRewards)} /> ))}
diff --git a/client/src/components/Home/HomeGridEpochResults/EpochResultsDetails/EpochResultsDetails.tsx b/client/src/components/Home/HomeGridEpochResults/EpochResultsDetails/EpochResultsDetails.tsx index eb0994cd2e..9574e09294 100644 --- a/client/src/components/Home/HomeGridEpochResults/EpochResultsDetails/EpochResultsDetails.tsx +++ b/client/src/components/Home/HomeGridEpochResults/EpochResultsDetails/EpochResultsDetails.tsx @@ -30,7 +30,7 @@ const EpochResultsDetails: FC = ({ details }) => { ? getValuesToDisplay({ cryptoCurrency: 'ethereum', showCryptoSuffix: true, - valueCrypto: details.donations, + valueCrypto: details.matchedRewards, }).primary : null; @@ -38,7 +38,7 @@ const EpochResultsDetails: FC = ({ details }) => { ? getValuesToDisplay({ cryptoCurrency: 'ethereum', showCryptoSuffix: true, - valueCrypto: details.donations + details.matchingFund, + valueCrypto: details.totalValueOfAllocations, }).primary : null; diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpoch.tsx b/client/src/components/Metrics/MetricsEpoch/MetricsEpoch.tsx index 035b7f2f29..1a3cb81586 100644 --- a/client/src/components/Metrics/MetricsEpoch/MetricsEpoch.tsx +++ b/client/src/components/Metrics/MetricsEpoch/MetricsEpoch.tsx @@ -93,7 +93,6 @@ const MetricsEpoch = (): ReactElement => { (acc, curr) => acc + curr.matchedRewards, 0n, ); - const matchingFund = matchedRewards > 0n ? matchedRewards - patronsRewards : 0n; // All metrics should be visible in the same moment (design). Skeletons are visible to the end of fetching all needed data. const isLoading = @@ -136,26 +135,26 @@ const MetricsEpoch = (): ReactElement => { {epoch < 4 && ( = ({ - totalUserDonationsWithPatronRewards, + totalUserDonations, isLoading, matchingFund, className, }) => { const { t } = useTranslation('translation', { keyPrefix: 'views.metrics' }); - const totalUserDonationWithPatronRewardsNumber = parseFloat( - formatUnitsBigInt(totalUserDonationsWithPatronRewards), - ); + const totalUserDonationsNumber = parseFloat(formatUnitsBigInt(totalUserDonations)); const matchingFundNumber = parseFloat(formatUnitsBigInt(matchingFund)); const donationsValue = - totalUserDonationWithPatronRewardsNumber > 0 - ? (totalUserDonationWithPatronRewardsNumber / - (matchingFundNumber + totalUserDonationWithPatronRewardsNumber)) * - 100 + totalUserDonationsNumber > 0 + ? (totalUserDonationsNumber / (matchingFundNumber + totalUserDonationsNumber)) * 100 : 0; return ( diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridDonationsVsMatching/types.ts b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridDonationsVsMatching/types.ts index 523af1c839..0eba7fbf13 100644 --- a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridDonationsVsMatching/types.ts +++ b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridDonationsVsMatching/types.ts @@ -2,5 +2,5 @@ export default interface MetricsEpochGridDonationsVsMatchingProps { className?: string; isLoading: boolean; matchingFund: bigint; - totalUserDonationsWithPatronRewards: bigint; + totalUserDonations: bigint; } diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridDonationsVsPersonalAllocations/MetricsEpochGridDonationsVsPersonalAllocations.tsx b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridDonationsVsPersonalAllocations/MetricsEpochGridDonationsVsPersonalAllocations.tsx index 38e18bdb75..41b6e2c72e 100644 --- a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridDonationsVsPersonalAllocations/MetricsEpochGridDonationsVsPersonalAllocations.tsx +++ b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridDonationsVsPersonalAllocations/MetricsEpochGridDonationsVsPersonalAllocations.tsx @@ -9,19 +9,15 @@ import MetricsEpochGridDonationsVsPersonalAllocationsProps from './types'; const MetricsEpochGridDonationsVsPersonalAllocations: FC< MetricsEpochGridDonationsVsPersonalAllocationsProps -> = ({ totalUserDonationsWithPatronRewards, isLoading, totalPersonal, className }) => { +> = ({ totalUserDonations, isLoading, totalPersonal, className }) => { const { t } = useTranslation('translation', { keyPrefix: 'views.metrics' }); - const totalUserDonationWithPatronRewardsNumber = parseFloat( - formatUnitsBigInt(totalUserDonationsWithPatronRewards), - ); + const totalUserDonationsNumber = parseFloat(formatUnitsBigInt(totalUserDonations)); const totalPersonalNumber = parseFloat(formatUnitsBigInt(totalPersonal)); const donationsValue = - totalUserDonationWithPatronRewardsNumber > 0 - ? (totalUserDonationWithPatronRewardsNumber / - (totalPersonalNumber + totalUserDonationWithPatronRewardsNumber)) * - 100 + totalUserDonationsNumber > 0 + ? (totalUserDonationsNumber / (totalPersonalNumber + totalUserDonationsNumber)) * 100 : 0; return ( diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridDonationsVsPersonalAllocations/types.ts b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridDonationsVsPersonalAllocations/types.ts index 0fd229fde9..7885c3098b 100644 --- a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridDonationsVsPersonalAllocations/types.ts +++ b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridDonationsVsPersonalAllocations/types.ts @@ -2,5 +2,5 @@ export default interface MetricsEpochGridDonationsVsPersonalAllocationsProps { className?: string; isLoading: boolean; totalPersonal: bigint; - totalUserDonationsWithPatronRewards: bigint; + totalUserDonations: bigint; } diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalDonations/MetricsEpochGridTotalDonations.tsx b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalDonations/MetricsEpochGridTotalDonations.tsx index d082503428..e0eaeea362 100644 --- a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalDonations/MetricsEpochGridTotalDonations.tsx +++ b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalDonations/MetricsEpochGridTotalDonations.tsx @@ -9,17 +9,17 @@ import MetricsEpochGridTotalDonationsProps from './types'; const MetricsEpochGridTotalDonations: FC = ({ isLoading, - totalUserDonationsWithPatronRewards, + totalUserDonations, className, }) => { const { t } = useTranslation('translation', { keyPrefix: 'views.metrics' }); const getValuesToDisplay = useGetValuesToDisplay(); - const totalUserDonationWithPatronRewardsValues = getValuesToDisplay({ + const totalUserDonationsValues = getValuesToDisplay({ cryptoCurrency: 'ethereum', showCryptoSuffix: true, - valueCrypto: totalUserDonationsWithPatronRewards, + valueCrypto: totalUserDonations, }); return ( @@ -33,8 +33,8 @@ const MetricsEpochGridTotalDonations: FC = dataTest="MetricsEpochGridTotalDonations__MetricsGridTileValue" isLoading={isLoading} size="S" - subvalue={totalUserDonationWithPatronRewardsValues.secondary} - value={totalUserDonationWithPatronRewardsValues.primary} + subvalue={totalUserDonationsValues.secondary} + value={totalUserDonationsValues.primary} /> ), title: t('totalDonations'), diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalDonations/types.ts b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalDonations/types.ts index 70faebc853..8cc8cc1510 100644 --- a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalDonations/types.ts +++ b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalDonations/types.ts @@ -1,5 +1,5 @@ export default interface MetricsEpochGridTotalDonationsProps { className?: string; isLoading: boolean; - totalUserDonationsWithPatronRewards: bigint; + totalUserDonations: bigint; } diff --git a/client/src/components/Metrics/MetricsProjectsList/MetricsProjectsList.tsx b/client/src/components/Metrics/MetricsProjectsList/MetricsProjectsList.tsx index de406b0ccc..2a5efbe3f3 100644 --- a/client/src/components/Metrics/MetricsProjectsList/MetricsProjectsList.tsx +++ b/client/src/components/Metrics/MetricsProjectsList/MetricsProjectsList.tsx @@ -48,7 +48,7 @@ const MetricsProjectsList: FC = ({ shouldIgnoreWei: true, }, showCryptoSuffix: true, - valueCrypto: project.matchingFund, + valueCrypto: project.matchedRewards, }).primary } name={project.name!} diff --git a/client/src/hooks/queries/useProjectsIpfsWithRewards.ts b/client/src/hooks/queries/useProjectsIpfsWithRewards.ts index ee00b8802d..41861bd273 100644 --- a/client/src/hooks/queries/useProjectsIpfsWithRewards.ts +++ b/client/src/hooks/queries/useProjectsIpfsWithRewards.ts @@ -2,9 +2,6 @@ import { ExtendedProject } from 'types/extended-project'; import getSortedElementsByTotalValueOfAllocationsAndAlphabetical from 'utils/getSortedElementsByTotalValueOfAllocationsAndAlphabetical'; import useProjectsDonors from './donors/useProjectsDonors'; -import useCurrentEpoch from './useCurrentEpoch'; -import useEpochInfo from './useEpochInfo'; -import useIsDecisionWindowOpen from './useIsDecisionWindowOpen'; import useMatchedProjectRewards from './useMatchedProjectRewards'; import useProjectsEpoch from './useProjectsEpoch'; import useProjectsIpfs from './useProjectsIpfs'; @@ -13,7 +10,6 @@ export interface ProjectIpfsWithRewards extends ExtendedProject { address: string; donations: bigint; matchedRewards: bigint; - matchingFund: bigint; numberOfDonors: number; percentage: number | undefined; totalValueOfAllocations: bigint; @@ -24,8 +20,6 @@ export default function useProjectsIpfsWithRewards(epoch?: number): { isAnyIpfsError: boolean; isFetching: boolean; } { - const { data: currentEpoch } = useCurrentEpoch(); - const { data: isDecisionWindowOpen } = useIsDecisionWindowOpen(); const { data: projectsAddresses, isFetching: isFetchingProjectsContract } = useProjectsEpoch(epoch); const { @@ -45,16 +39,11 @@ export default function useProjectsIpfsWithRewards(epoch?: number): { isSuccess: isSuccessProjectsDonors, } = useProjectsDonors(epoch); - const { data: epochInfo, isFetching: isFetchingEpochInfo } = useEpochInfo( - epoch ?? (isDecisionWindowOpen ? currentEpoch! - 1 : currentEpoch!), - ); - const isFetching = isFetchingProjectsContract || isFetchingProjectsIpfs || (isFetchingMatchedProjectRewards && !isRefetchingMatchedProjectRewards) || - isFetchingProjectsDonors || - isFetchingEpochInfo; + isFetchingProjectsDonors; if (isFetching) { return { data: [], @@ -63,14 +52,12 @@ export default function useProjectsIpfsWithRewards(epoch?: number): { }; } - const patronsRewards = epochInfo?.patronsRewards || BigInt(0); - const projectsWithRewards = (projectsIpfs || []).map(project => { const projectMatchedProjectRewards = matchedProjectRewards?.find( ({ address }) => address === project.address, ); - const matchedRewards = projectMatchedProjectRewards?.matched || 0n; + const matchedRewards = projectMatchedProjectRewards?.matched ?? 0n; /** * For epochs finalized projectMatchedProjectRewards contains data only for projects that @@ -84,10 +71,9 @@ export default function useProjectsIpfsWithRewards(epoch?: number): { return { donations, matchedRewards, - matchingFund: matchedRewards - patronsRewards, numberOfDonors: isSuccessProjectsDonors ? projectsDonors[project.address]?.length || 0 : 0, percentage: projectMatchedProjectRewards?.percentage, - totalValueOfAllocations: donations + (projectMatchedProjectRewards?.matched ?? 0n), + totalValueOfAllocations: donations + matchedRewards, ...project, }; }); diff --git a/client/src/mocks/subgraph/projects.ts b/client/src/mocks/subgraph/projects.ts index b7e5a15a0f..e921042e5a 100644 --- a/client/src/mocks/subgraph/projects.ts +++ b/client/src/mocks/subgraph/projects.ts @@ -5,7 +5,6 @@ export const mockedProjectATotalValueOfAllocations1: ProjectIpfsWithRewards = { donations: BigInt(1), isLoadingError: false, matchedRewards: BigInt(1), - matchingFund: BigInt(1), name: 'A', numberOfDonors: 10, percentage: 1, From 6ab6ba67c0c259f1d109b3fead37227f16ca003b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Miko=C5=82ajczyk?= Date: Mon, 23 Sep 2024 13:33:33 +0200 Subject: [PATCH 112/241] oct-1931: ignore gwei/wei --- .../EpochResults/EpochResults.tsx | 2 +- .../EpochResultsDetails/EpochResultsDetails.tsx | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/client/src/components/Home/HomeGridEpochResults/EpochResults/EpochResults.tsx b/client/src/components/Home/HomeGridEpochResults/EpochResults/EpochResults.tsx index cc41147a86..a6c92612dc 100644 --- a/client/src/components/Home/HomeGridEpochResults/EpochResults/EpochResults.tsx +++ b/client/src/components/Home/HomeGridEpochResults/EpochResults/EpochResults.tsx @@ -12,7 +12,7 @@ import useProjectsIpfsWithRewards, { import styles from './EpochResults.module.scss'; const EpochResults: FC<{ epoch: number }> = ({ epoch }) => { - const { data: projectsIpfsWithRewards } = useProjectsIpfsWithRewards(); + const { data: projectsIpfsWithRewards } = useProjectsIpfsWithRewards(epoch); const [highlightedBarAddress, setHighlightedBarAddress] = useState(null); const { ipfsGateways } = env; diff --git a/client/src/components/Home/HomeGridEpochResults/EpochResultsDetails/EpochResultsDetails.tsx b/client/src/components/Home/HomeGridEpochResults/EpochResultsDetails/EpochResultsDetails.tsx index 9574e09294..1f44449640 100644 --- a/client/src/components/Home/HomeGridEpochResults/EpochResultsDetails/EpochResultsDetails.tsx +++ b/client/src/components/Home/HomeGridEpochResults/EpochResultsDetails/EpochResultsDetails.tsx @@ -21,6 +21,10 @@ const EpochResultsDetails: FC = ({ details }) => { const donationsToDisplay = details ? getValuesToDisplay({ cryptoCurrency: 'ethereum', + getFormattedEthValueProps: { + shouldIgnoreGwei: true, + shouldIgnoreWei: true, + }, showCryptoSuffix: true, valueCrypto: details.donations, }).primary @@ -29,6 +33,10 @@ const EpochResultsDetails: FC = ({ details }) => { const matchingToDisplay = details ? getValuesToDisplay({ cryptoCurrency: 'ethereum', + getFormattedEthValueProps: { + shouldIgnoreGwei: true, + shouldIgnoreWei: true, + }, showCryptoSuffix: true, valueCrypto: details.matchedRewards, }).primary @@ -37,6 +45,10 @@ const EpochResultsDetails: FC = ({ details }) => { const totalToDisplay = details ? getValuesToDisplay({ cryptoCurrency: 'ethereum', + getFormattedEthValueProps: { + shouldIgnoreGwei: true, + shouldIgnoreWei: true, + }, showCryptoSuffix: true, valueCrypto: details.totalValueOfAllocations, }).primary From 3c50bb73ee4a0eceaa57f666c7809ceaf082f56a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Miko=C5=82ajczyk?= Date: Mon, 23 Sep 2024 15:02:32 +0200 Subject: [PATCH 113/241] oct-1931: footer-content gap fix --- .../Metrics/MetricsGeneral/MetricsGeneral.module.scss | 5 ----- client/src/components/shared/Layout/Layout.module.scss | 7 ++++++- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/client/src/components/Metrics/MetricsGeneral/MetricsGeneral.module.scss b/client/src/components/Metrics/MetricsGeneral/MetricsGeneral.module.scss index 110a552aca..22d1c0e201 100644 --- a/client/src/components/Metrics/MetricsGeneral/MetricsGeneral.module.scss +++ b/client/src/components/Metrics/MetricsGeneral/MetricsGeneral.module.scss @@ -1,10 +1,5 @@ .root { width: 100%; - padding-bottom: 4rem; - - @media #{$large-desktop-up} { - padding-bottom: 5.6rem; - } } .divider { diff --git a/client/src/components/shared/Layout/Layout.module.scss b/client/src/components/shared/Layout/Layout.module.scss index 10222366a1..eeb76acd1c 100644 --- a/client/src/components/shared/Layout/Layout.module.scss +++ b/client/src/components/shared/Layout/Layout.module.scss @@ -16,7 +16,6 @@ @media #{$large-desktop-up} { width: $layout-section-width-large-desktop; - margin: 0 auto; } } @@ -44,6 +43,12 @@ @media #{$desktop-up} { padding: 0 $layout-horizontal-padding-large; } + + .section { + @media #{$large-desktop-up} { + margin: 0 auto; + } + } } } From 1f4f5ccf10848fba51ef7fd807b55dc52d0b5a94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Miko=C5=82ajczyk?= Date: Tue, 24 Sep 2024 09:22:45 +0200 Subject: [PATCH 114/241] oct-1882: cr fixes --- .../MetricsProjectsListItem.module.scss | 1 - .../MetricsProjectsListItem/MetricsProjectsListItem.tsx | 8 ++++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/client/src/components/Metrics/MetricsProjectsListItem/MetricsProjectsListItem.module.scss b/client/src/components/Metrics/MetricsProjectsListItem/MetricsProjectsListItem.module.scss index 0831cf5d11..e3c6f2ce6c 100644 --- a/client/src/components/Metrics/MetricsProjectsListItem/MetricsProjectsListItem.module.scss +++ b/client/src/components/Metrics/MetricsProjectsListItem/MetricsProjectsListItem.module.scss @@ -1,6 +1,5 @@ .root { display: grid; - // display: flex; align-items: center; justify-content: space-between; height: 5.6rem; diff --git a/client/src/components/Metrics/MetricsProjectsListItem/MetricsProjectsListItem.tsx b/client/src/components/Metrics/MetricsProjectsListItem/MetricsProjectsListItem.tsx index 5b1dd7e38d..7338b72ebd 100644 --- a/client/src/components/Metrics/MetricsProjectsListItem/MetricsProjectsListItem.tsx +++ b/client/src/components/Metrics/MetricsProjectsListItem/MetricsProjectsListItem.tsx @@ -39,18 +39,18 @@ const MetricsProjectsListItem: FC = ({
{isLargeDesktop && ( <> -
+
{numberOfDonors}
-
+
{donations}
-
+
{matchFunding}
)} -
+
{total}
From 1c8bbe0a90dd96087e86845615de51f154a7e9cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Zi=C3=B3=C5=82ek?= Date: Thu, 19 Sep 2024 13:50:32 +0200 Subject: [PATCH 115/241] fix: ProjectsView random s character --- client/src/views/ProjectsView/ProjectsView.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/client/src/views/ProjectsView/ProjectsView.tsx b/client/src/views/ProjectsView/ProjectsView.tsx index 878f1f0663..2e85efdb91 100644 --- a/client/src/views/ProjectsView/ProjectsView.tsx +++ b/client/src/views/ProjectsView/ProjectsView.tsx @@ -160,7 +160,6 @@ const ProjectsView = (): ReactElement => { variant="underselect" />
- s {!areCurrentEpochsProjectsHiddenOutsideAllocationWindow && ( Date: Fri, 20 Sep 2024 13:03:05 +0200 Subject: [PATCH 116/241] fix: i18n key --- client/src/views/ProjectsView/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/views/ProjectsView/utils.ts b/client/src/views/ProjectsView/utils.ts index 8bb61032e0..1e3e4d37ae 100644 --- a/client/src/views/ProjectsView/utils.ts +++ b/client/src/views/ProjectsView/utils.ts @@ -4,7 +4,7 @@ import { Option } from 'components/ui/InputSelect/types'; export const ORDER_OPTIONS = (t: TFunction): Option[] => [ { - label: t('sortOptions.randomised'), + label: t('sortOptions.randomized'), value: 'randomized', }, { From ff74862025c1d883880a51b84adfe017849b48a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Miko=C5=82ajczyk?= Date: Thu, 19 Sep 2024 16:24:51 +0200 Subject: [PATCH 117/241] oct-1857: z-index changes --- client/src/components/shared/Layout/Layout.module.scss | 2 +- client/src/components/ui/Drawer/Drawer.module.scss | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/components/shared/Layout/Layout.module.scss b/client/src/components/shared/Layout/Layout.module.scss index 453063aebd..10222366a1 100644 --- a/client/src/components/shared/Layout/Layout.module.scss +++ b/client/src/components/shared/Layout/Layout.module.scss @@ -26,7 +26,7 @@ background: $color-octant-grey3; width: 100%; padding: 0 $layout-horizontal-padding-small; - z-index: $z-index-8; + z-index: $z-index-5; backdrop-filter: blur(2.5rem); &.isPatronMode { diff --git a/client/src/components/ui/Drawer/Drawer.module.scss b/client/src/components/ui/Drawer/Drawer.module.scss index 30ce9b42ab..279abf4399 100644 --- a/client/src/components/ui/Drawer/Drawer.module.scss +++ b/client/src/components/ui/Drawer/Drawer.module.scss @@ -6,7 +6,7 @@ right: 0; background: $color-octant-grey6; top: 0; - z-index: $z-index-8; + z-index: $z-index-5; box-shadow: 0 0 5rem 0 $color-black-08; .buttonClose { From d86039b1dc16ade0302fdd4bd3841031c65a3bf5 Mon Sep 17 00:00:00 2001 From: Housekeeper Bot Date: Fri, 20 Sep 2024 07:37:48 +0000 Subject: [PATCH 118/241] [CI/CD] Update uat.env contracts --- ci/argocd/contracts/uat.env | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/ci/argocd/contracts/uat.env b/ci/argocd/contracts/uat.env index bebd723cfa..e08a716201 100644 --- a/ci/argocd/contracts/uat.env +++ b/ci/argocd/contracts/uat.env @@ -1,8 +1,8 @@ -BLOCK_NUMBER=6683543 +BLOCK_NUMBER=6725985 GLM_CONTRACT_ADDRESS=0x71432DD1ae7DB41706ee6a22148446087BdD0906 -AUTH_CONTRACT_ADDRESS=0x8bCbFdc1e53a2fC55b64ab17b494b7Bc06100323 -DEPOSITS_CONTRACT_ADDRESS=0x6fBcF5081Fa8796f17d5a3598452693e4cf9B3a3 -EPOCHS_CONTRACT_ADDRESS=0x3CCBBa4359874ff0d0Bd4C494A556f2aff3470fd -PROPOSALS_CONTRACT_ADDRESS=0x1CaE72eFe2f769F85B88e5229c13DE12e86aE714 -WITHDRAWALS_TARGET_CONTRACT_ADDRESS=0x3b71Ff1b760F0998A85a79864a06F6254CB7FFBd -VAULT_CONTRACT_ADDRESS=0x690B3c3D9ea25524AAE2Dc85f04B189C1c2DfF5b +AUTH_CONTRACT_ADDRESS=0x8aa3e15BBA708d46B5e3Bd786A899b054260ECDC +DEPOSITS_CONTRACT_ADDRESS=0x0db12DE3E3f6887A4308BBC81b873f0D3c08D62F +EPOCHS_CONTRACT_ADDRESS=0xfec3fCa4bE643d961dCd327C411d90DF4E0F12B8 +PROPOSALS_CONTRACT_ADDRESS=0xB41D5fc5D9059127e5256Bbb77Df9d25d0373372 +WITHDRAWALS_TARGET_CONTRACT_ADDRESS=0x881D5A2003e8ba59189e50eBe1E9dEf1F8d1C469 +VAULT_CONTRACT_ADDRESS=0x821Dfe7AE82d2cEC385c00D5F785c74f5434cE3A From e54dbc5737db591fd7de9b4db29e3c03e3663f90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Miko=C5=82ajczyk?= Date: Fri, 20 Sep 2024 15:44:57 +0200 Subject: [PATCH 119/241] oct-1857: randomized projects fix --- .../Projects/ProjectsList/ProjectsList.tsx | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/client/src/components/Projects/ProjectsList/ProjectsList.tsx b/client/src/components/Projects/ProjectsList/ProjectsList.tsx index ae9615f4b7..028b651ce9 100644 --- a/client/src/components/Projects/ProjectsList/ProjectsList.tsx +++ b/client/src/components/Projects/ProjectsList/ProjectsList.tsx @@ -8,7 +8,9 @@ import Grid from 'components/shared/Grid'; import { PROJECTS_ADDRESSES_RANDOMIZED_ORDER } from 'constants/localStorageKeys'; import useEpochDurationLabel from 'hooks/helpers/useEpochDurationLabel'; import useProjectsEpoch from 'hooks/queries/useProjectsEpoch'; -import useProjectsIpfsWithRewards from 'hooks/queries/useProjectsIpfsWithRewards'; +import useProjectsIpfsWithRewards, { + ProjectIpfsWithRewards, +} from 'hooks/queries/useProjectsIpfsWithRewards'; import { ProjectsAddressesRandomizedOrder } from 'types/localStorage'; import styles from './ProjectsList.module.scss'; @@ -31,13 +33,21 @@ const ProjectsList: FC = ({ const isLoading = isFetchingProjectsEpoch || isFetchingProjectsWithRewards; - const projectsIpfsWithRewardsSorted = useMemo(() => { + const projectsIpfsWithRewardsSorted = useMemo((): ProjectIpfsWithRewards[] => { switch (orderOption) { case 'randomized': { const projectsAddressesRandomizedOrder = JSON.parse( localStorage.getItem(PROJECTS_ADDRESSES_RANDOMIZED_ORDER)!, ) as ProjectsAddressesRandomizedOrder; - return projectsAddressesRandomizedOrder.addressesRandomizedOrder; + + const { addressesRandomizedOrder } = projectsAddressesRandomizedOrder; + + return projectsIpfsWithRewards.sort((a, b) => { + return ( + addressesRandomizedOrder.indexOf(a.address) - + addressesRandomizedOrder.indexOf(b.address) + ); + }); } case 'alphabeticalAscending': { const projectIpfsWithRewardsFiltered = projectsIpfsWithRewards.filter( From 496465b7e2c37af8300d644afafac694ee738111 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Miko=C5=82ajczyk?= Date: Fri, 20 Sep 2024 16:47:47 +0200 Subject: [PATCH 120/241] oct-1857: prevent fetching userAllocations when AW is open --- client/src/components/Allocation/Allocation.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/client/src/components/Allocation/Allocation.tsx b/client/src/components/Allocation/Allocation.tsx index 9af4baf002..8b53b25527 100644 --- a/client/src/components/Allocation/Allocation.tsx +++ b/client/src/components/Allocation/Allocation.tsx @@ -103,6 +103,9 @@ const Allocation = (): ReactElement => { const [showLowUQScoreModal, setShowLowUQScoreModal] = useState(false); const { refetch: refetchEpochAllocations } = useEpochAllocations( isDecisionWindowOpen ? currentEpoch! - 1 : currentEpoch!, + { + enabled: isDecisionWindowOpen === true, + }, ); const { isDesktop } = useMediaQuery(); From 1ae119845ff93349ef1c0bb1ca6aaa304d4a7488 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Miko=C5=82ajczyk?= Date: Fri, 20 Sep 2024 16:50:35 +0200 Subject: [PATCH 121/241] oct-1857: open/close allocation drawer by clicking edit button on home donations tile --- .../components/Home/HomeGridDonations/HomeGridDonations.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/client/src/components/Home/HomeGridDonations/HomeGridDonations.tsx b/client/src/components/Home/HomeGridDonations/HomeGridDonations.tsx index d75dd1bfe8..6dcf091507 100644 --- a/client/src/components/Home/HomeGridDonations/HomeGridDonations.tsx +++ b/client/src/components/Home/HomeGridDonations/HomeGridDonations.tsx @@ -29,7 +29,8 @@ const HomeGridDonations: FC = ({ className }) => { const { data: userAllocations, isFetching: isFetchingUserAllocations } = useUserAllocations(); const { data: currentEpoch } = useCurrentEpoch(); const { data: isDecisionWindowOpen } = useIsDecisionWindowOpen(); - const { setIsAllocationDrawerOpen } = useLayoutStore(state => ({ + const { isAllocationDrawerOpen, setIsAllocationDrawerOpen } = useLayoutStore(state => ({ + isAllocationDrawerOpen: state.data.isAllocationDrawerOpen, setIsAllocationDrawerOpen: state.setIsAllocationDrawerOpen, })); @@ -62,7 +63,7 @@ const HomeGridDonations: FC = ({ className }) => { className={styles.editButton} onClick={() => { setCurrentView('edit'); - setIsAllocationDrawerOpen(true); + setIsAllocationDrawerOpen(!isAllocationDrawerOpen); }} variant="cta" > From 809f1036e1497705f5ca6e8e4d20fbb05d9d24f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Miko=C5=82ajczyk?= Date: Fri, 20 Sep 2024 17:25:38 +0200 Subject: [PATCH 122/241] oct-1857: prevent fetching userAllocations and currentEpoch each time --- client/src/hooks/queries/useCurrentEpoch.ts | 1 + client/src/hooks/queries/useUserAllocations.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/client/src/hooks/queries/useCurrentEpoch.ts b/client/src/hooks/queries/useCurrentEpoch.ts index c7ca5cfef2..8dd7e6f6db 100644 --- a/client/src/hooks/queries/useCurrentEpoch.ts +++ b/client/src/hooks/queries/useCurrentEpoch.ts @@ -18,6 +18,7 @@ export default function useCurrentEpoch( }), queryKey: QUERY_KEYS.currentEpoch, select: res => Number(res), + staleTime: Infinity, ...options, }); } diff --git a/client/src/hooks/queries/useUserAllocations.ts b/client/src/hooks/queries/useUserAllocations.ts index 65adfdb11c..0d354ab2cd 100644 --- a/client/src/hooks/queries/useUserAllocations.ts +++ b/client/src/hooks/queries/useUserAllocations.ts @@ -43,6 +43,7 @@ export default function useUserAllocations( isManuallyEdited: !!response.isManuallyEdited, }; }, + staleTime: Infinity, ...options, }); } From 2035fab598c343e3c3be48b3f7edd7c5787dacf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Miko=C5=82ajczyk?= Date: Tue, 24 Sep 2024 16:11:56 +0200 Subject: [PATCH 123/241] oct-1971: client refactor 1 --- .../Allocation/AllocationItem/types.ts | 9 +- .../AllocationNavigation.tsx | 4 +- .../AllocationSliderBox.tsx | 2 +- .../EarnBoxGlmLock/EarnBoxGlmLock.module.scss | 17 - .../Earn/EarnBoxGlmLock/EarnBoxGlmLock.tsx | 141 ------- .../components/Earn/EarnBoxGlmLock/types.ts | 3 - .../EarnBoxPersonalAllocation.module.scss | 15 - .../EarnBoxPersonalAllocation.tsx | 162 -------- .../Earn/EarnBoxPersonalAllocation/index.tsx | 2 - .../Earn/EarnBoxPersonalAllocation/types.ts | 3 - .../Earn/EarnGlmLock/EarnGlmLock.module.scss | 10 - .../Earn/EarnGlmLock/EarnGlmLock.tsx | 249 ------------ .../EarnGlmLockBudget.module.scss | 3 - .../EarnGlmLockBudget/EarnGlmLockBudget.tsx | 26 -- .../EarnGlmLock/EarnGlmLockBudget/index.tsx | 2 - .../EarnGlmLock/EarnGlmLockBudget/types.ts | 3 - .../EarnGlmLockBudgetBox.module.scss | 65 --- .../EarnGlmLockBudgetBox.tsx | 73 ---- .../EarnGlmLockBudgetBox/index.tsx | 2 - .../EarnGlmLock/EarnGlmLockBudgetBox/types.ts | 5 - .../EarnGlmLockNotification.module.scss | 35 -- .../EarnGlmLockNotification.tsx | 96 ----- .../EarnGlmLockNotification/index.tsx | 2 - .../EarnGlmLockNotification/types.ts | 9 - .../EarnGlmLockStepper/EarnGlmLockStepper.tsx | 32 -- .../EarnGlmLock/EarnGlmLockStepper/index.tsx | 2 - .../EarnGlmLock/EarnGlmLockStepper/types.ts | 7 - .../EarnGlmLockTabs.module.scss | 52 --- .../EarnGlmLockTabs/EarnGlmLockTabs.tsx | 177 -------- .../EarnGlmLock/EarnGlmLockTabs/index.tsx | 2 - .../Earn/EarnGlmLock/EarnGlmLockTabs/types.ts | 18 - .../EarnGlmLockTabsInputs.module.scss | 19 - .../EarnGlmLockTabsInputs.tsx | 149 ------- .../EarnGlmLockTabsInputs/index.tsx | 2 - .../EarnGlmLockTabsInputs/types.ts | 20 - .../src/components/Earn/EarnGlmLock/types.ts | 24 -- .../src/components/Earn/EarnGlmLock/utils.ts | 41 -- .../Earn/EarnHistory/EarnHistory.module.scss | 43 -- .../Earn/EarnHistory/EarnHistory.tsx | 73 ---- .../EarnHistoryItem.module.scss | 41 -- .../EarnHistoryItem/EarnHistoryItem.tsx | 103 ----- .../EarnHistory/EarnHistoryItem/index.tsx | 2 - .../Earn/EarnHistory/EarnHistoryItem/types.ts | 13 - .../EarnHistoryItemDateAndTime.module.scss | 3 - .../EarnHistoryItemDateAndTime.tsx | 11 - .../EarnHistoryItemDateAndTime/index.tsx | 2 - .../EarnHistoryItemDateAndTime/types.ts | 3 - .../EarnHistoryItemDateAndTime/utils.ts | 6 - .../EarnHistoryItemDetails.tsx | 15 - ...rnHistoryItemDetailsAllocation.module.scss | 22 - .../EarnHistoryItemDetailsAllocation.tsx | 152 ------- .../index.tsx | 2 - .../EarnHistoryItemDetailsAllocation/types.ts | 8 - .../EarnHistoryItemDetailsRest.module.scss | 8 - .../EarnHistoryItemDetailsRest.tsx | 92 ----- .../EarnHistoryItemDetailsRest/index.tsx | 2 - .../EarnHistoryItemDetailsRest/types.ts | 5 - .../EarnHistoryItemDetails/index.tsx | 2 - .../EarnHistoryItemDetails/types.ts | 5 - .../EarnHistoryItemDetailsModal.tsx | 50 --- .../EarnHistoryItemDetailsModal/index.tsx | 2 - .../EarnHistoryItemDetailsModal/types.ts | 6 - .../EarnHistoryList.module.scss | 10 - .../EarnHistoryList/EarnHistoryList.tsx | 26 -- .../EarnHistory/EarnHistoryList/index.tsx | 2 - .../Earn/EarnHistory/EarnHistoryList/types.ts | 5 - .../EarnHistorySkeleton.module.scss | 30 -- .../EarnHistorySkeleton.tsx | 21 - .../EarnHistory/EarnHistorySkeleton/index.tsx | 2 - .../EarnHistoryTransactionLabel.module.scss | 17 - .../EarnHistoryTransactionLabel.tsx | 30 -- .../EarnHistoryTransactionLabel/index.tsx | 2 - .../EarnHistoryTransactionLabel/types.ts | 4 - .../src/components/Earn/EarnHistory/types.ts | 3 - .../EarnRewardsCalculator.module.scss | 3 - .../EarnRewardsCalculator.tsx | 146 ------- ...rdsCalculatorEpochDaysSelector.module.scss | 74 ---- ...EarnRewardsCalculatorEpochDaysSelector.tsx | 58 --- .../index.tsx | 2 - .../types.ts | 4 - ...EarnRewardsCalculatorEstimates.module.scss | 56 --- .../EarnRewardsCalculatorEstimates.tsx | 52 --- .../EarnRewardsCalculatorEstimates/index.tsx | 2 - .../EarnRewardsCalculatorEstimates/types.ts | 7 - ...arnRewardsCalculatorUqSelector.module.scss | 63 --- .../EarnRewardsCalculatorUqSelector.tsx | 56 --- .../EarnRewardsCalculatorUqSelector/index.tsx | 2 - .../EarnRewardsCalculatorUqSelector/types.ts | 4 - .../Earn/EarnRewardsCalculator/index.tsx | 2 - .../Earn/EarnRewardsCalculator/types.ts | 5 - .../EarnTipTiles/EarnTipTiles.module.scss | 27 -- .../Earn/EarnTipTiles/EarnTipTiles.tsx | 115 ------ .../EarnWithdrawEth.module.scss | 20 - .../Earn/EarnWithdrawEth/EarnWithdrawEth.tsx | 101 ----- .../components/Earn/EarnWithdrawEth/index.tsx | 2 - .../components/Earn/EarnWithdrawEth/types.ts | 3 - .../ModalEarnGlmLock/ModalEarnGlmLock.tsx | 29 -- .../Earn/ModalEarnGlmLock/index.tsx | 2 - .../components/Earn/ModalEarnGlmLock/types.ts | 5 - .../ModalEarnRewardsCalculator.module.scss | 21 - .../ModalEarnRewardsCalculator.tsx | 48 --- .../Earn/ModalEarnRewardsCalculator/index.tsx | 2 - .../Earn/ModalEarnRewardsCalculator/types.ts | 5 - .../ModalEarnWithdrawEth.tsx | 21 - .../Earn/ModalEarnWithdrawEth/index.tsx | 2 - .../Earn/ModalEarnWithdrawEth/types.ts | 5 - .../HomeGridCurrentGlmLock.tsx | 2 +- .../HomeGridDonations/HomeGridDonations.tsx | 8 +- .../EpochResultsDetails.tsx | 8 +- .../HomeGridPersonalAllocation.tsx | 2 +- .../WithdrawEth/WithdrawEth.tsx | 2 +- .../HomeGridRewardsEstimator.tsx | 5 +- .../Home/HomeGridRewardsEstimator/types.ts | 6 + .../HomeGridRewardsEstimator}/utils.ts | 0 .../ModalTransactionDetails.tsx | 6 +- .../TransactionDetailsAllocation.tsx | 4 +- .../TransactionDetailsAllocation/types.ts | 4 +- .../TransactionDetailsDateAndTime/types.ts | 2 +- .../TransactionDetailsRest.tsx | 4 +- .../TransactionDetailsRest/types.ts | 4 +- .../TransactionLabel/TransactionLabel.tsx | 4 +- .../TransactionsListItem.tsx | 4 +- .../Home/HomeGridUQScore/HomeGridUQScore.tsx | 2 +- .../Home/HomeRewards/HomeRewards.tsx | 6 +- .../MetricsEpochGridDonationsVsMatching.tsx | 4 +- ...pochGridDonationsVsPersonalAllocations.tsx | 4 +- .../MetricsEpochGridTopProjects.tsx | 10 +- .../ButtonAddToAllocate.tsx | 4 +- .../src/components/shared/Layout/Layout.tsx | 217 +--------- .../LayoutNavbar/LayoutNavbar.module.scss | 3 +- .../shared/ViewTitle/ViewTitle.module.scss | 8 + client/src/constants/store.ts | 1 - client/src/hooks/events/useAllocate.ts | 2 +- .../src/hooks/helpers/useNavigationTabs.tsx | 4 +- .../hooks/mutations/useAllocateSimulate.ts | 2 +- client/src/hooks/utils/utils.ts | 2 +- client/src/locales/en/translation.json | 263 +----------- client/src/routes/RootRoutes/RootRoutes.tsx | 14 +- client/src/routes/RootRoutes/routes.ts | 2 - client/src/styles/utils/_keyframes.scss | 63 ++- client/src/styles/utils/_mixins.scss | 17 - client/src/styles/utils/_variables.scss | 1 - client/src/svg/history.ts | 13 - client/src/svg/misc.ts | 18 - client/src/svg/navigation.ts | 6 - .../AllocationView/AllocationView.module.scss | 28 -- .../AllocationView}/index.tsx | 2 +- client/src/views/AllocationView/types.ts | 21 - client/src/views/AllocationView/utils.test.ts | 379 ------------------ client/src/views/AllocationView/utils.ts | 292 -------------- .../src/views/EarnView/EarnView.module.scss | 38 -- client/src/views/EarnView/EarnView.tsx | 66 --- .../EarnGlmLock => views/HomeView}/index.tsx | 2 +- .../views/MetricsView/MetricsView.module.scss | 22 - client/src/views/MetricsView/MetricsView.tsx | 7 +- .../MetricsView}/index.tsx | 2 +- client/src/views/PlaygroundView/index.tsx | 2 + .../ProjectView}/index.tsx | 2 +- client/src/views/ProjectsView/index.tsx | 2 + client/src/views/SettingsView/index.tsx | 2 + client/src/views/SyncView/index.tsx | 2 + 161 files changed, 140 insertions(+), 4710 deletions(-) delete mode 100644 client/src/components/Earn/EarnBoxGlmLock/EarnBoxGlmLock.module.scss delete mode 100644 client/src/components/Earn/EarnBoxGlmLock/EarnBoxGlmLock.tsx delete mode 100644 client/src/components/Earn/EarnBoxGlmLock/types.ts delete mode 100644 client/src/components/Earn/EarnBoxPersonalAllocation/EarnBoxPersonalAllocation.module.scss delete mode 100644 client/src/components/Earn/EarnBoxPersonalAllocation/EarnBoxPersonalAllocation.tsx delete mode 100644 client/src/components/Earn/EarnBoxPersonalAllocation/index.tsx delete mode 100644 client/src/components/Earn/EarnBoxPersonalAllocation/types.ts delete mode 100644 client/src/components/Earn/EarnGlmLock/EarnGlmLock.module.scss delete mode 100644 client/src/components/Earn/EarnGlmLock/EarnGlmLock.tsx delete mode 100644 client/src/components/Earn/EarnGlmLock/EarnGlmLockBudget/EarnGlmLockBudget.module.scss delete mode 100644 client/src/components/Earn/EarnGlmLock/EarnGlmLockBudget/EarnGlmLockBudget.tsx delete mode 100644 client/src/components/Earn/EarnGlmLock/EarnGlmLockBudget/index.tsx delete mode 100644 client/src/components/Earn/EarnGlmLock/EarnGlmLockBudget/types.ts delete mode 100644 client/src/components/Earn/EarnGlmLock/EarnGlmLockBudgetBox/EarnGlmLockBudgetBox.module.scss delete mode 100644 client/src/components/Earn/EarnGlmLock/EarnGlmLockBudgetBox/EarnGlmLockBudgetBox.tsx delete mode 100644 client/src/components/Earn/EarnGlmLock/EarnGlmLockBudgetBox/index.tsx delete mode 100644 client/src/components/Earn/EarnGlmLock/EarnGlmLockBudgetBox/types.ts delete mode 100644 client/src/components/Earn/EarnGlmLock/EarnGlmLockNotification/EarnGlmLockNotification.module.scss delete mode 100644 client/src/components/Earn/EarnGlmLock/EarnGlmLockNotification/EarnGlmLockNotification.tsx delete mode 100644 client/src/components/Earn/EarnGlmLock/EarnGlmLockNotification/index.tsx delete mode 100644 client/src/components/Earn/EarnGlmLock/EarnGlmLockNotification/types.ts delete mode 100644 client/src/components/Earn/EarnGlmLock/EarnGlmLockStepper/EarnGlmLockStepper.tsx delete mode 100644 client/src/components/Earn/EarnGlmLock/EarnGlmLockStepper/index.tsx delete mode 100644 client/src/components/Earn/EarnGlmLock/EarnGlmLockStepper/types.ts delete mode 100644 client/src/components/Earn/EarnGlmLock/EarnGlmLockTabs/EarnGlmLockTabs.module.scss delete mode 100644 client/src/components/Earn/EarnGlmLock/EarnGlmLockTabs/EarnGlmLockTabs.tsx delete mode 100644 client/src/components/Earn/EarnGlmLock/EarnGlmLockTabs/index.tsx delete mode 100644 client/src/components/Earn/EarnGlmLock/EarnGlmLockTabs/types.ts delete mode 100644 client/src/components/Earn/EarnGlmLock/EarnGlmLockTabsInputs/EarnGlmLockTabsInputs.module.scss delete mode 100644 client/src/components/Earn/EarnGlmLock/EarnGlmLockTabsInputs/EarnGlmLockTabsInputs.tsx delete mode 100644 client/src/components/Earn/EarnGlmLock/EarnGlmLockTabsInputs/index.tsx delete mode 100644 client/src/components/Earn/EarnGlmLock/EarnGlmLockTabsInputs/types.ts delete mode 100644 client/src/components/Earn/EarnGlmLock/types.ts delete mode 100644 client/src/components/Earn/EarnGlmLock/utils.ts delete mode 100644 client/src/components/Earn/EarnHistory/EarnHistory.module.scss delete mode 100644 client/src/components/Earn/EarnHistory/EarnHistory.tsx delete mode 100644 client/src/components/Earn/EarnHistory/EarnHistoryItem/EarnHistoryItem.module.scss delete mode 100644 client/src/components/Earn/EarnHistory/EarnHistoryItem/EarnHistoryItem.tsx delete mode 100644 client/src/components/Earn/EarnHistory/EarnHistoryItem/index.tsx delete mode 100644 client/src/components/Earn/EarnHistory/EarnHistoryItem/types.ts delete mode 100644 client/src/components/Earn/EarnHistory/EarnHistoryItemDetails/EarnHistoryItemDateAndTime/EarnHistoryItemDateAndTime.module.scss delete mode 100644 client/src/components/Earn/EarnHistory/EarnHistoryItemDetails/EarnHistoryItemDateAndTime/EarnHistoryItemDateAndTime.tsx delete mode 100644 client/src/components/Earn/EarnHistory/EarnHistoryItemDetails/EarnHistoryItemDateAndTime/index.tsx delete mode 100644 client/src/components/Earn/EarnHistory/EarnHistoryItemDetails/EarnHistoryItemDateAndTime/types.ts delete mode 100644 client/src/components/Earn/EarnHistory/EarnHistoryItemDetails/EarnHistoryItemDateAndTime/utils.ts delete mode 100644 client/src/components/Earn/EarnHistory/EarnHistoryItemDetails/EarnHistoryItemDetails.tsx delete mode 100644 client/src/components/Earn/EarnHistory/EarnHistoryItemDetails/EarnHistoryItemDetailsAllocation/EarnHistoryItemDetailsAllocation.module.scss delete mode 100644 client/src/components/Earn/EarnHistory/EarnHistoryItemDetails/EarnHistoryItemDetailsAllocation/EarnHistoryItemDetailsAllocation.tsx delete mode 100644 client/src/components/Earn/EarnHistory/EarnHistoryItemDetails/EarnHistoryItemDetailsAllocation/index.tsx delete mode 100644 client/src/components/Earn/EarnHistory/EarnHistoryItemDetails/EarnHistoryItemDetailsAllocation/types.ts delete mode 100644 client/src/components/Earn/EarnHistory/EarnHistoryItemDetails/EarnHistoryItemDetailsRest/EarnHistoryItemDetailsRest.module.scss delete mode 100644 client/src/components/Earn/EarnHistory/EarnHistoryItemDetails/EarnHistoryItemDetailsRest/EarnHistoryItemDetailsRest.tsx delete mode 100644 client/src/components/Earn/EarnHistory/EarnHistoryItemDetails/EarnHistoryItemDetailsRest/index.tsx delete mode 100644 client/src/components/Earn/EarnHistory/EarnHistoryItemDetails/EarnHistoryItemDetailsRest/types.ts delete mode 100644 client/src/components/Earn/EarnHistory/EarnHistoryItemDetails/index.tsx delete mode 100644 client/src/components/Earn/EarnHistory/EarnHistoryItemDetails/types.ts delete mode 100644 client/src/components/Earn/EarnHistory/EarnHistoryItemDetailsModal/EarnHistoryItemDetailsModal.tsx delete mode 100644 client/src/components/Earn/EarnHistory/EarnHistoryItemDetailsModal/index.tsx delete mode 100644 client/src/components/Earn/EarnHistory/EarnHistoryItemDetailsModal/types.ts delete mode 100644 client/src/components/Earn/EarnHistory/EarnHistoryList/EarnHistoryList.module.scss delete mode 100644 client/src/components/Earn/EarnHistory/EarnHistoryList/EarnHistoryList.tsx delete mode 100644 client/src/components/Earn/EarnHistory/EarnHistoryList/index.tsx delete mode 100644 client/src/components/Earn/EarnHistory/EarnHistoryList/types.ts delete mode 100644 client/src/components/Earn/EarnHistory/EarnHistorySkeleton/EarnHistorySkeleton.module.scss delete mode 100644 client/src/components/Earn/EarnHistory/EarnHistorySkeleton/EarnHistorySkeleton.tsx delete mode 100644 client/src/components/Earn/EarnHistory/EarnHistorySkeleton/index.tsx delete mode 100644 client/src/components/Earn/EarnHistory/EarnHistoryTransactionLabel/EarnHistoryTransactionLabel.module.scss delete mode 100644 client/src/components/Earn/EarnHistory/EarnHistoryTransactionLabel/EarnHistoryTransactionLabel.tsx delete mode 100644 client/src/components/Earn/EarnHistory/EarnHistoryTransactionLabel/index.tsx delete mode 100644 client/src/components/Earn/EarnHistory/EarnHistoryTransactionLabel/types.ts delete mode 100644 client/src/components/Earn/EarnHistory/types.ts delete mode 100644 client/src/components/Earn/EarnRewardsCalculator/EarnRewardsCalculator.module.scss delete mode 100644 client/src/components/Earn/EarnRewardsCalculator/EarnRewardsCalculator.tsx delete mode 100644 client/src/components/Earn/EarnRewardsCalculator/EarnRewardsCalculatorEpochDaysSelector/EarnRewardsCalculatorEpochDaysSelector.module.scss delete mode 100644 client/src/components/Earn/EarnRewardsCalculator/EarnRewardsCalculatorEpochDaysSelector/EarnRewardsCalculatorEpochDaysSelector.tsx delete mode 100644 client/src/components/Earn/EarnRewardsCalculator/EarnRewardsCalculatorEpochDaysSelector/index.tsx delete mode 100644 client/src/components/Earn/EarnRewardsCalculator/EarnRewardsCalculatorEpochDaysSelector/types.ts delete mode 100644 client/src/components/Earn/EarnRewardsCalculator/EarnRewardsCalculatorEstimates/EarnRewardsCalculatorEstimates.module.scss delete mode 100644 client/src/components/Earn/EarnRewardsCalculator/EarnRewardsCalculatorEstimates/EarnRewardsCalculatorEstimates.tsx delete mode 100644 client/src/components/Earn/EarnRewardsCalculator/EarnRewardsCalculatorEstimates/index.tsx delete mode 100644 client/src/components/Earn/EarnRewardsCalculator/EarnRewardsCalculatorEstimates/types.ts delete mode 100644 client/src/components/Earn/EarnRewardsCalculator/EarnRewardsCalculatorUqSelector/EarnRewardsCalculatorUqSelector.module.scss delete mode 100644 client/src/components/Earn/EarnRewardsCalculator/EarnRewardsCalculatorUqSelector/EarnRewardsCalculatorUqSelector.tsx delete mode 100644 client/src/components/Earn/EarnRewardsCalculator/EarnRewardsCalculatorUqSelector/index.tsx delete mode 100644 client/src/components/Earn/EarnRewardsCalculator/EarnRewardsCalculatorUqSelector/types.ts delete mode 100644 client/src/components/Earn/EarnRewardsCalculator/index.tsx delete mode 100644 client/src/components/Earn/EarnRewardsCalculator/types.ts delete mode 100644 client/src/components/Earn/EarnTipTiles/EarnTipTiles.module.scss delete mode 100644 client/src/components/Earn/EarnTipTiles/EarnTipTiles.tsx delete mode 100644 client/src/components/Earn/EarnWithdrawEth/EarnWithdrawEth.module.scss delete mode 100644 client/src/components/Earn/EarnWithdrawEth/EarnWithdrawEth.tsx delete mode 100644 client/src/components/Earn/EarnWithdrawEth/index.tsx delete mode 100644 client/src/components/Earn/EarnWithdrawEth/types.ts delete mode 100644 client/src/components/Earn/ModalEarnGlmLock/ModalEarnGlmLock.tsx delete mode 100644 client/src/components/Earn/ModalEarnGlmLock/index.tsx delete mode 100644 client/src/components/Earn/ModalEarnGlmLock/types.ts delete mode 100644 client/src/components/Earn/ModalEarnRewardsCalculator/ModalEarnRewardsCalculator.module.scss delete mode 100644 client/src/components/Earn/ModalEarnRewardsCalculator/ModalEarnRewardsCalculator.tsx delete mode 100644 client/src/components/Earn/ModalEarnRewardsCalculator/index.tsx delete mode 100644 client/src/components/Earn/ModalEarnRewardsCalculator/types.ts delete mode 100644 client/src/components/Earn/ModalEarnWithdrawEth/ModalEarnWithdrawEth.tsx delete mode 100644 client/src/components/Earn/ModalEarnWithdrawEth/index.tsx delete mode 100644 client/src/components/Earn/ModalEarnWithdrawEth/types.ts rename client/src/components/{Earn/EarnRewardsCalculator => Home/HomeGridRewardsEstimator}/utils.ts (100%) delete mode 100644 client/src/constants/store.ts delete mode 100644 client/src/svg/history.ts delete mode 100644 client/src/views/AllocationView/AllocationView.module.scss rename client/src/{components/Earn/EarnBoxGlmLock => views/AllocationView}/index.tsx (53%) delete mode 100644 client/src/views/AllocationView/types.ts delete mode 100644 client/src/views/AllocationView/utils.test.ts delete mode 100644 client/src/views/AllocationView/utils.ts delete mode 100644 client/src/views/EarnView/EarnView.module.scss delete mode 100644 client/src/views/EarnView/EarnView.tsx rename client/src/{components/Earn/EarnGlmLock => views/HomeView}/index.tsx (54%) delete mode 100644 client/src/views/MetricsView/MetricsView.module.scss rename client/src/{components/Earn/EarnHistory => views/MetricsView}/index.tsx (54%) create mode 100644 client/src/views/PlaygroundView/index.tsx rename client/src/{components/Earn/EarnTipTiles => views/ProjectView}/index.tsx (54%) create mode 100644 client/src/views/ProjectsView/index.tsx create mode 100644 client/src/views/SettingsView/index.tsx create mode 100644 client/src/views/SyncView/index.tsx diff --git a/client/src/components/Allocation/AllocationItem/types.ts b/client/src/components/Allocation/AllocationItem/types.ts index 5811ffcc8e..783ad16b40 100644 --- a/client/src/components/Allocation/AllocationItem/types.ts +++ b/client/src/components/Allocation/AllocationItem/types.ts @@ -1,8 +1,8 @@ import React from 'react'; import { LeverageMatched } from 'api/calls/allocate'; +import { AllocationValue } from 'components/Allocation/types'; import { ProjectIpfsWithRewards } from 'hooks/queries/useProjectsIpfsWithRewards'; -import { AllocationValue } from 'views/AllocationView/types'; export interface AllocationItemWithAllocations extends ProjectIpfsWithRewards { isAllocatedTo: boolean; @@ -12,12 +12,7 @@ export interface AllocationItemWithAllocations extends ProjectIpfsWithRewards { export default interface AllocationItemProps extends Omit< AllocationItemWithAllocations, - | 'totalValueOfAllocations' - | 'percentage' - | 'numberOfDonors' - | 'matchedRewards' - | 'donations' - | 'matchingFund' + 'totalValueOfAllocations' | 'percentage' | 'numberOfDonors' | 'matchedRewards' | 'donations' > { className?: string; isError: boolean; diff --git a/client/src/components/Allocation/AllocationNavigation/AllocationNavigation.tsx b/client/src/components/Allocation/AllocationNavigation/AllocationNavigation.tsx index 59f1e962e6..abfc9a584f 100644 --- a/client/src/components/Allocation/AllocationNavigation/AllocationNavigation.tsx +++ b/client/src/components/Allocation/AllocationNavigation/AllocationNavigation.tsx @@ -15,7 +15,7 @@ const AllocationNavigation: FC = ({ onResetValues, isWaitingForAllMultisigSignatures, }) => { - const { t } = useTranslation('translation', { + const { i18n, t } = useTranslation('translation', { keyPrefix: 'components.dedicated.allocationNavigation', }); @@ -39,7 +39,7 @@ const AllocationNavigation: FC = ({ onClick: onAllocate, } : { - label: t('edit'), + label: i18n.t('common.edit'), onClick: () => setCurrentView('edit'), }; diff --git a/client/src/components/Allocation/AllocationSliderBox/AllocationSliderBox.tsx b/client/src/components/Allocation/AllocationSliderBox/AllocationSliderBox.tsx index c2b5d5569a..e369bf39a7 100644 --- a/client/src/components/Allocation/AllocationSliderBox/AllocationSliderBox.tsx +++ b/client/src/components/Allocation/AllocationSliderBox/AllocationSliderBox.tsx @@ -80,7 +80,7 @@ const AllocationSliderBox: FC = ({ : 50; const sections = [ { - header: isLocked ? t('donated') : t('donate'), + header: isLocked ? i18n.t('common.donated') : t('donate'), value: getValuesToDisplay({ cryptoCurrency: 'ethereum', showCryptoSuffix: true, diff --git a/client/src/components/Earn/EarnBoxGlmLock/EarnBoxGlmLock.module.scss b/client/src/components/Earn/EarnBoxGlmLock/EarnBoxGlmLock.module.scss deleted file mode 100644 index 2708f078c1..0000000000 --- a/client/src/components/Earn/EarnBoxGlmLock/EarnBoxGlmLock.module.scss +++ /dev/null @@ -1,17 +0,0 @@ -.tooltip { - width: 10.4rem !important; -} - -.calculateRewards { - display: flex; - align-items: center; - justify-content: center; - width: 3.2rem; - height: 3.2rem; - background: $color-octant-grey8; - border-radius: $border-radius-10; -} - -.effectiveTooltip { - padding: 1.8rem 1.4rem 1.4rem 2.4rem !important; -} diff --git a/client/src/components/Earn/EarnBoxGlmLock/EarnBoxGlmLock.tsx b/client/src/components/Earn/EarnBoxGlmLock/EarnBoxGlmLock.tsx deleted file mode 100644 index c3801b49af..0000000000 --- a/client/src/components/Earn/EarnBoxGlmLock/EarnBoxGlmLock.tsx +++ /dev/null @@ -1,141 +0,0 @@ -import _first from 'lodash/first'; -import React, { FC, Fragment, useState } from 'react'; -import { useTranslation } from 'react-i18next'; -import { useAccount } from 'wagmi'; - -import ModalEarnGlmLock from 'components/Earn/ModalEarnGlmLock/ModalEarnGlmLock'; -import ModalEarnRewardsCalculator from 'components/Earn/ModalEarnRewardsCalculator'; -import BoxRounded from 'components/ui/BoxRounded'; -import Sections from 'components/ui/BoxRounded/Sections/Sections'; -import { SectionProps } from 'components/ui/BoxRounded/Sections/types'; -import Svg from 'components/ui/Svg'; -import Tooltip from 'components/ui/Tooltip'; -import useCurrentEpoch from 'hooks/queries/useCurrentEpoch'; -import useDepositValue from 'hooks/queries/useDepositValue'; -import useEstimatedEffectiveDeposit from 'hooks/queries/useEstimatedEffectiveDeposit'; -import useTransactionLocalStore from 'store/transactionLocal/store'; -import { calculator } from 'svg/misc'; -import getIsPreLaunch from 'utils/getIsPreLaunch'; - -import styles from './EarnBoxGlmLock.module.scss'; -import EarnBoxGlmLockProps from './types'; - -const EarnBoxGlmLock: FC = ({ classNameBox }) => { - const { t, i18n } = useTranslation('translation', { - keyPrefix: 'components.dedicated.boxGlmLock', - }); - const { isConnected } = useAccount(); - const { isAppWaitingForTransactionToBeIndexed, transactionsPending } = useTransactionLocalStore( - state => ({ - isAppWaitingForTransactionToBeIndexed: state.data.isAppWaitingForTransactionToBeIndexed, - transactionsPending: state.data.transactionsPending, - }), - ); - - const [isModalGlmLockOpen, setIsModalGlmLockOpen] = useState(false); - const { data: estimatedEffectiveDeposit, isFetching: isFetchingEstimatedEffectiveDeposit } = - useEstimatedEffectiveDeposit(); - const [isModalRewardsCalculatorOpen, setIsModalRewardsCalculatorOpen] = useState(false); - const { data: depositsValue, isFetching: isFetchingDepositValue } = useDepositValue(); - const { data: currentEpoch } = useCurrentEpoch(); - - const isPreLaunch = getIsPreLaunch(currentEpoch); - - const sections: SectionProps[] = [ - { - dataTest: 'BoxGlmLock__Section--current', - doubleValueProps: { - cryptoCurrency: 'golem', - dataTest: 'BoxGlmLock__Section--current__DoubleValue', - isFetching: - isFetchingDepositValue || - (isAppWaitingForTransactionToBeIndexed && - _first(transactionsPending)?.type !== 'withdrawal'), - showCryptoSuffix: true, - valueCrypto: depositsValue, - }, - isDisabled: isPreLaunch && !isConnected, - label: t('current'), - }, - { - dataTest: 'BoxGlmLock__Section--effective', - doubleValueProps: { - coinPricesServerDowntimeText: '...', - cryptoCurrency: 'golem', - dataTest: 'BoxGlmLock__Section--effective__DoubleValue', - isFetching: - isFetchingEstimatedEffectiveDeposit || - (isAppWaitingForTransactionToBeIndexed && - _first(transactionsPending)?.type !== 'withdrawal'), - showCryptoSuffix: true, - valueCrypto: estimatedEffectiveDeposit, - }, - isDisabled: isPreLaunch && !isConnected, - label: t('effective'), - tooltipProps: { - dataTest: 'TooltipEffectiveLockedBalance', - position: 'bottom-right', - text: t('tooltipText'), - tooltipClassName: styles.effectiveTooltip, - }, - }, - ]; - - return ( - - setIsModalGlmLockOpen(true), - variant: 'cta', - }} - className={classNameBox} - dataTest="BoxGlmLock__BoxRounded" - hasSections - isVertical - title={t('lockedBalance')} - titleSuffix={ - -
setIsModalRewardsCalculatorOpen(true)} - > - -
-
- } - > - -
- setIsModalGlmLockOpen(false), - }} - /> - setIsModalRewardsCalculatorOpen(false), - }} - /> -
- ); -}; - -export default EarnBoxGlmLock; diff --git a/client/src/components/Earn/EarnBoxGlmLock/types.ts b/client/src/components/Earn/EarnBoxGlmLock/types.ts deleted file mode 100644 index 10b646c2a4..0000000000 --- a/client/src/components/Earn/EarnBoxGlmLock/types.ts +++ /dev/null @@ -1,3 +0,0 @@ -export default interface EarnBoxGlmLockProps { - classNameBox: string; -} diff --git a/client/src/components/Earn/EarnBoxPersonalAllocation/EarnBoxPersonalAllocation.module.scss b/client/src/components/Earn/EarnBoxPersonalAllocation/EarnBoxPersonalAllocation.module.scss deleted file mode 100644 index 7130b1e153..0000000000 --- a/client/src/components/Earn/EarnBoxPersonalAllocation/EarnBoxPersonalAllocation.module.scss +++ /dev/null @@ -1,15 +0,0 @@ -.pendingTooltip { - margin-left: -2.6rem; -} - -.pendingTooltipLabel { - font-size: $font-size-12; - font-weight: $font-weight-semibold; - line-height: 2rem; -} - -.pendingTooltipDate { - font-size: $font-size-20; - font-weight: $font-weight-semibold; - margin: 0.8rem 0 0.4rem; -} diff --git a/client/src/components/Earn/EarnBoxPersonalAllocation/EarnBoxPersonalAllocation.tsx b/client/src/components/Earn/EarnBoxPersonalAllocation/EarnBoxPersonalAllocation.tsx deleted file mode 100644 index c2dfb6ee78..0000000000 --- a/client/src/components/Earn/EarnBoxPersonalAllocation/EarnBoxPersonalAllocation.tsx +++ /dev/null @@ -1,162 +0,0 @@ -import { format } from 'date-fns'; -import _first from 'lodash/first'; -import React, { FC, Fragment, useMemo, useState } from 'react'; -import { useTranslation } from 'react-i18next'; -import { useAccount } from 'wagmi'; - -import ModalEarnWithdrawEth from 'components/Earn/ModalEarnWithdrawEth'; -import BoxRounded from 'components/ui/BoxRounded'; -import Sections from 'components/ui/BoxRounded/Sections/Sections'; -import { SectionProps } from 'components/ui/BoxRounded/Sections/types'; -import useEpochAndAllocationTimestamps from 'hooks/helpers/useEpochAndAllocationTimestamps'; -import useIsProjectAdminMode from 'hooks/helpers/useIsProjectAdminMode'; -import useTotalPatronDonations from 'hooks/helpers/useTotalPatronDonations'; -import useCurrentEpoch from 'hooks/queries/useCurrentEpoch'; -import useCurrentEpochProps from 'hooks/queries/useCurrentEpochProps'; -import useIndividualReward from 'hooks/queries/useIndividualReward'; -import useIsDecisionWindowOpen from 'hooks/queries/useIsDecisionWindowOpen'; -import useIsPatronMode from 'hooks/queries/useIsPatronMode'; -import useWithdrawals from 'hooks/queries/useWithdrawals'; -import useTransactionLocalStore from 'store/transactionLocal/store'; -import getIsPreLaunch from 'utils/getIsPreLaunch'; - -import styles from './EarnBoxPersonalAllocation.module.scss'; -import EarnBoxPersonalAllocationProps from './types'; - -const EarnBoxPersonalAllocation: FC = ({ className }) => { - const { i18n, t } = useTranslation('translation', { - keyPrefix: 'components.dedicated.boxPersonalAllocation', - }); - const [isModalOpen, setIsModalOpen] = useState(false); - const { isConnected } = useAccount(); - const { data: currentEpoch } = useCurrentEpoch(); - const { data: isDecisionWindowOpen } = useIsDecisionWindowOpen(); - const { timeCurrentEpochStart, timeCurrentAllocationEnd } = useEpochAndAllocationTimestamps(); - const { data: currentEpochProps } = useCurrentEpochProps(); - const { data: withdrawals, isFetching: isFetchingWithdrawals } = useWithdrawals(); - const { data: individualReward, isFetching: isFetchingIndividualReward } = useIndividualReward(); - const { data: isPatronMode } = useIsPatronMode(); - const { data: totalPatronDonations, isFetching: isFetchingTotalPatronDonations } = - useTotalPatronDonations({ isEnabledAdditional: !!isPatronMode }); - const { isAppWaitingForTransactionToBeIndexed, transactionsPending } = useTransactionLocalStore( - state => ({ - isAppWaitingForTransactionToBeIndexed: state.data.isAppWaitingForTransactionToBeIndexed, - transactionsPending: state.data.transactionsPending, - }), - ); - - const isPreLaunch = getIsPreLaunch(currentEpoch); - const isProjectAdminMode = useIsProjectAdminMode(); - - const sections: SectionProps[] = [ - ...(!isProjectAdminMode - ? [ - { - dataTest: 'BoxPersonalAllocation__Section', - doubleValueProps: { - cryptoCurrency: 'ethereum', - dataTest: 'BoxPersonalAllocation__Section--pending__DoubleValue', - isFetching: - (isPatronMode ? isFetchingIndividualReward : isFetchingWithdrawals) || - (isAppWaitingForTransactionToBeIndexed && - _first(transactionsPending)?.type === 'withdrawal'), - showCryptoSuffix: true, - valueCrypto: isPatronMode ? individualReward : withdrawals?.sums.pending, - }, - label: isPatronMode ? t('currentEpoch') : t('pending'), - tooltipProps: isPatronMode - ? undefined - : { - position: 'bottom-right', - text: ( -
-
- {t('pendingFundsAvailableAfter')} -
-
- {/* TODO OCT-1041 fetch next epoch props instead of assuming the same length */} - {currentEpochProps && timeCurrentEpochStart && timeCurrentAllocationEnd - ? format( - new Date( - isDecisionWindowOpen - ? timeCurrentAllocationEnd - : // When AW is closed, it's when the last AW closed. - timeCurrentEpochStart + currentEpochProps.decisionWindow, - ), - 'haaa z, d LLLL', - ) - : ''} -
-
- ), - }, - } as SectionProps, - ] - : []), - { - dataTest: 'BoxPersonalAllocation__Section', - doubleValueProps: { - coinPricesServerDowntimeText: !isProjectAdminMode ? '...' : undefined, - cryptoCurrency: 'ethereum', - dataTest: 'BoxPersonalAllocation__Section--availableNow__DoubleValue', - isFetching: - (isPatronMode ? isFetchingTotalPatronDonations : isFetchingWithdrawals) || - (isAppWaitingForTransactionToBeIndexed && - _first(transactionsPending)?.type === 'withdrawal'), - showCryptoSuffix: true, - valueCrypto: isPatronMode ? totalPatronDonations?.value : withdrawals?.sums.available, - }, - label: isPatronMode && !isProjectAdminMode ? t('allTime') : i18n.t('common.availableNow'), - }, - ]; - - const title = useMemo(() => { - if (isProjectAdminMode) { - return i18n.t('common.donations'); - } - if (isPatronMode) { - return t('patronEarnings'); - } - return i18n.t('common.personalAllocation'); - }, [isProjectAdminMode, isPatronMode, i18n, t]); - - return ( - - setIsModalOpen(true), - variant: isProjectAdminMode ? 'cta' : 'secondary', - } - } - className={className} - dataTest="BoxPersonalAllocation" - hasSections - isVertical - title={title} - > - - - setIsModalOpen(false), - }} - /> - - ); -}; - -export default EarnBoxPersonalAllocation; diff --git a/client/src/components/Earn/EarnBoxPersonalAllocation/index.tsx b/client/src/components/Earn/EarnBoxPersonalAllocation/index.tsx deleted file mode 100644 index dedc208a26..0000000000 --- a/client/src/components/Earn/EarnBoxPersonalAllocation/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line no-restricted-exports -export { default } from './EarnBoxPersonalAllocation'; diff --git a/client/src/components/Earn/EarnBoxPersonalAllocation/types.ts b/client/src/components/Earn/EarnBoxPersonalAllocation/types.ts deleted file mode 100644 index c73db828a2..0000000000 --- a/client/src/components/Earn/EarnBoxPersonalAllocation/types.ts +++ /dev/null @@ -1,3 +0,0 @@ -export default interface EarnBoxPersonalAllocationProps { - className?: string; -} diff --git a/client/src/components/Earn/EarnGlmLock/EarnGlmLock.module.scss b/client/src/components/Earn/EarnGlmLock/EarnGlmLock.module.scss deleted file mode 100644 index 2f5b7fd1fa..0000000000 --- a/client/src/components/Earn/EarnGlmLock/EarnGlmLock.module.scss +++ /dev/null @@ -1,10 +0,0 @@ -.form { - width: 100%; - height: 100%; -} - -.element { - &:not(:last-child) { - margin: 0 auto 1.6rem; - } -} diff --git a/client/src/components/Earn/EarnGlmLock/EarnGlmLock.tsx b/client/src/components/Earn/EarnGlmLock/EarnGlmLock.tsx deleted file mode 100644 index 3a966b3735..0000000000 --- a/client/src/components/Earn/EarnGlmLock/EarnGlmLock.tsx +++ /dev/null @@ -1,249 +0,0 @@ -import { Formik } from 'formik'; -import React, { FC, useEffect, useRef, useState } from 'react'; -import { useTranslation } from 'react-i18next'; -import { useAccount, useWalletClient, usePublicClient, useWaitForTransactionReceipt } from 'wagmi'; - -import { apiGetSafeTransactions } from 'api/calls/safeTransactions'; -import EarnGlmLockBudget from 'components/Earn/EarnGlmLock/EarnGlmLockBudget'; -import EarnGlmLockNotification from 'components/Earn/EarnGlmLock/EarnGlmLockNotification'; -import EarnGlmLockStepper from 'components/Earn/EarnGlmLock/EarnGlmLockStepper'; -import EarnGlmLockTabs from 'components/Earn/EarnGlmLock/EarnGlmLockTabs'; -import networkConfig from 'constants/networkConfig'; -import env from 'env'; -import { writeContractERC20 } from 'hooks/contracts/writeContracts'; -import useAvailableFundsGlm from 'hooks/helpers/useAvailableFundsGlm'; -import useApprovalState, { ApprovalState } from 'hooks/helpers/useMaxApproveCallback'; -import useMediaQuery from 'hooks/helpers/useMediaQuery'; -import useLock from 'hooks/mutations/useLock'; -import useUnlock from 'hooks/mutations/useUnlock'; -import useDepositValue from 'hooks/queries/useDepositValue'; -import useEstimatedEffectiveDeposit from 'hooks/queries/useEstimatedEffectiveDeposit'; -import useHistory from 'hooks/queries/useHistory'; -import useIsContract from 'hooks/queries/useIsContract'; -import useProjectsEpoch from 'hooks/queries/useProjectsEpoch'; -import useLockedSummaryLatest from 'hooks/subgraph/useLockedSummaryLatest'; -import toastService from 'services/toastService'; -import useTransactionLocalStore from 'store/transactionLocal/store'; -import { parseUnitsBigInt } from 'utils/parseUnitsBigInt'; - -import styles from './EarnGlmLock.module.scss'; -import EarnGlmLockProps, { Step, OnReset } from './types'; -import { formInitialValues, validationSchema } from './utils'; - -const EarnGlmLock: FC = ({ currentMode, onCurrentModeChange, onCloseModal }) => { - const { i18n } = useTranslation(); - const { address } = useAccount(); - const publicClient = usePublicClient({ chainId: networkConfig.id }); - const { data: walletClient } = useWalletClient(); - const { isDesktop } = useMediaQuery(); - const { data: isContract } = useIsContract(); - const [transactionHashForEtherscan, setTransactionHashForEtherscan] = useState< - string | undefined - >(undefined); - const { addTransactionPending } = useTransactionLocalStore(state => ({ - addTransactionPending: state.addTransactionPending, - })); - const { data: transactionReceipt, isLoading: isLoadingTransactionReceipt } = - useWaitForTransactionReceipt({ - hash: transactionHashForEtherscan as `0x${string}` | undefined, - onReplaced: response => - setTransactionHashForEtherscan(response.transactionReceipt.transactionHash), - }); - - /** - * Value to depose so that we don't ask for allowance when user - * requests less than already approved. - */ - const [valueToDepose, setValueToDepose] = useState(BigInt(0)); - const [step, setStep] = useState(1); - const [intervalId, setIntervalId] = useState(null); - const [isCryptoOrFiatInputFocused, setIsCryptoOrFiatInputFocused] = useState(true); - const buttonUseMaxRef = useRef(null); - - const { data: availableFundsGlm } = useAvailableFundsGlm(); - const { data: projectsEpoch } = useProjectsEpoch(); - const { refetch: refetchHistory } = useHistory(); - const { data: depositsValue, refetch: refetchDeposit } = useDepositValue(); - const { refetch: refetchEstimatedEffectiveDeposit } = useEstimatedEffectiveDeposit(); - const { refetch: refetchLockedSummaryLatest } = useLockedSummaryLatest(); - - useEffect(() => { - if (transactionReceipt && !isLoadingTransactionReceipt) { - setStep(3); - } - }, [transactionReceipt, isLoadingTransactionReceipt, setStep]); - - const [approvalState, approveCallback] = useApprovalState( - address, - env.contractDepositsAddress, - valueToDepose, - ); - - const isButtonUseMaxFocused = document.activeElement === buttonUseMaxRef.current; - /** - * When input is focused isCryptoOrFiatInputFocused is true. - * Clicking "use max" blurs inputs, setting isCryptoOrFiatInputFocused to false. - * EarnGlmLockTabs onMax sets the focus back on inputs, triggering isCryptoOrFiatInputFocused to true. - * - * Between second and third update flickering can occur, when focus is already set to input, - * but state didn't update yet. - * - * To check it out set isAnyInputFocused to permanent "false" and click "use max" fast. - */ - const isAnyInputFocused = document.activeElement?.tagName === 'INPUT'; - const showBudgetBox = - isDesktop || - (!isDesktop && !isCryptoOrFiatInputFocused && !isButtonUseMaxFocused && !isAnyInputFocused); - - const onMutate = async (): Promise => { - if (!publicClient || !walletClient || !availableFundsGlm) { - return; - } - - setStep(2); - - const approvalStateCurrent = await approveCallback(); - if (currentMode === 'lock' && approvalStateCurrent !== ApprovalState.APPROVED) { - const hash = await writeContractERC20({ - args: [env.contractDepositsAddress, availableFundsGlm.value], - functionName: 'approve', - walletClient, - }); - await publicClient.waitForTransactionReceipt({ hash }); - } - }; - - const onSuccess = async ({ hash, value }): Promise => { - if (isContract) { - const id = setInterval(async () => { - const nextSafeTransactions = await apiGetSafeTransactions(address!); - const safeTransaction = nextSafeTransactions.results.find( - t => t.safeTxHash === hash && t.transactionHash, - ); - - if (safeTransaction) { - clearInterval(id); - Promise.all([ - refetchDeposit(), - refetchEstimatedEffectiveDeposit(), - refetchLockedSummaryLatest(), - refetchHistory(), - ]).then(() => { - setTransactionHashForEtherscan(safeTransaction.transactionHash); - setStep(3); - }); - } - }, 2000); - setIntervalId(id); - return; - } - addTransactionPending({ - eventData: { - amount: value, - transactionHash: hash, - }, - isMultisig: !!isContract, - // GET /history uses seconds. Normalization here. - timestamp: Math.floor(Date.now() / 1000).toString(), - type: currentMode, - }); - setTransactionHashForEtherscan(hash); - }; - - const onReset: OnReset = ({ setFieldValue, newMode = 'lock' }) => { - onCurrentModeChange(newMode); - setTransactionHashForEtherscan(undefined); - setStep(1); - - if (setFieldValue) { - setFieldValue('currentMode', newMode); - } - }; - - const onError = () => onReset({ newMode: currentMode }); - - const lockMutation = useLock({ onError, onMutate, onSuccess }); - const unlockMutation = useUnlock({ onError, onMutate, onSuccess }); - - const onApproveOrDeposit = async ({ valueToDeposeOrWithdraw }): Promise => { - const isSignedInAsAProject = projectsEpoch!.projectsAddresses.includes(address!); - - if (isSignedInAsAProject) { - toastService.showToast({ - name: 'projectForbiddenOperation', - title: i18n.t('common.projectForbiddenOperation'), - type: 'error', - }); - return; - } - - const valueToDeposeOrWithdrawBigInt = parseUnitsBigInt(valueToDeposeOrWithdraw); - if (currentMode === 'lock') { - await lockMutation.mutateAsync(valueToDeposeOrWithdrawBigInt); - } else { - await unlockMutation.mutateAsync(valueToDeposeOrWithdrawBigInt); - } - }; - - const isLockingApproved = approvalState === ApprovalState.APPROVED; - const isApprovalKnown = approvalState !== ApprovalState.UNKNOWN; - - useEffect(() => { - return () => { - if (!intervalId) { - return; - } - clearInterval(intervalId); - }; - }, [intervalId]); - - return ( - - {props => ( -
- {isDesktop && ( - - )} - {(step === 2 && currentMode === 'lock' && isApprovalKnown && !isLockingApproved) || - step === 3 ? ( - - ) : ( - - )} - - - )} -
- ); -}; - -export default EarnGlmLock; diff --git a/client/src/components/Earn/EarnGlmLock/EarnGlmLockBudget/EarnGlmLockBudget.module.scss b/client/src/components/Earn/EarnGlmLock/EarnGlmLockBudget/EarnGlmLockBudget.module.scss deleted file mode 100644 index 2b4bc5cd05..0000000000 --- a/client/src/components/Earn/EarnGlmLock/EarnGlmLockBudget/EarnGlmLockBudget.module.scss +++ /dev/null @@ -1,3 +0,0 @@ -.root { - margin: 0 auto 1.6rem -} diff --git a/client/src/components/Earn/EarnGlmLock/EarnGlmLockBudget/EarnGlmLockBudget.tsx b/client/src/components/Earn/EarnGlmLock/EarnGlmLockBudget/EarnGlmLockBudget.tsx deleted file mode 100644 index 2dc3545210..0000000000 --- a/client/src/components/Earn/EarnGlmLock/EarnGlmLockBudget/EarnGlmLockBudget.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { useFormikContext } from 'formik'; -import React, { FC } from 'react'; - -import EarnGlmLockBudgetBox from 'components/Earn/EarnGlmLock/EarnGlmLockBudgetBox'; -import { FormFields } from 'components/Earn/EarnGlmLock/types'; - -import styles from './EarnGlmLockBudget.module.scss'; -import EarnGlmLockBudgetProps from './types'; - -const EarnGlmLockBudget: FC = ({ isVisible }) => { - const { errors } = useFormikContext(); - - if (!isVisible) { - return null; - } - - return ( - - ); -}; - -export default EarnGlmLockBudget; diff --git a/client/src/components/Earn/EarnGlmLock/EarnGlmLockBudget/index.tsx b/client/src/components/Earn/EarnGlmLock/EarnGlmLockBudget/index.tsx deleted file mode 100644 index 5a45b6c2e0..0000000000 --- a/client/src/components/Earn/EarnGlmLock/EarnGlmLockBudget/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line no-restricted-exports -export { default } from './EarnGlmLockBudget'; diff --git a/client/src/components/Earn/EarnGlmLock/EarnGlmLockBudget/types.ts b/client/src/components/Earn/EarnGlmLock/EarnGlmLockBudget/types.ts deleted file mode 100644 index 4be2791aed..0000000000 --- a/client/src/components/Earn/EarnGlmLock/EarnGlmLockBudget/types.ts +++ /dev/null @@ -1,3 +0,0 @@ -export default interface EarnGlmLockBudgetProps { - isVisible: boolean; -} diff --git a/client/src/components/Earn/EarnGlmLock/EarnGlmLockBudgetBox/EarnGlmLockBudgetBox.module.scss b/client/src/components/Earn/EarnGlmLock/EarnGlmLockBudgetBox/EarnGlmLockBudgetBox.module.scss deleted file mode 100644 index 8762572265..0000000000 --- a/client/src/components/Earn/EarnGlmLock/EarnGlmLockBudgetBox/EarnGlmLockBudgetBox.module.scss +++ /dev/null @@ -1,65 +0,0 @@ -.transactionHash { - display: flex; - justify-content: center; - margin: 1rem 0 0 0; -} - -.availableFunds { - height: 3.2rem; - display: flex; - align-items: flex-end; - border-top: 0.1rem solid $color-white; - margin-top: 1.6rem; - - .value { - color: $color-black; - - &.isError { - color: $color-octant-orange; - } - } -} - -.button { - padding: 0; -} - -.budgetRow { - position: relative; - height: 5.6rem; - display: flex; - align-items: center; - justify-content: space-between; - width: 100%; - font-size: $font-size-14; - font-weight: $font-weight-bold; - - &:not(:last-child)::after { - content: ''; - width: 100%; - position: absolute; - height: 0.1rem; - background-color: $color-octant-grey1; - bottom: 0; - left: 0; - } - - .skeleton { - @include skeleton(); - height: 1.7rem; - width: 10rem; - } - - - .budgetLabel { - color: $color-octant-grey5; - } - - .budgetValue { - color: $color-octant-dark; - - &.isError { - color: $color-octant-orange; - } - } -} diff --git a/client/src/components/Earn/EarnGlmLock/EarnGlmLockBudgetBox/EarnGlmLockBudgetBox.tsx b/client/src/components/Earn/EarnGlmLock/EarnGlmLockBudgetBox/EarnGlmLockBudgetBox.tsx deleted file mode 100644 index 37733eaf77..0000000000 --- a/client/src/components/Earn/EarnGlmLock/EarnGlmLockBudgetBox/EarnGlmLockBudgetBox.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import cx from 'classnames'; -import React, { FC, useMemo } from 'react'; -import { useTranslation } from 'react-i18next'; - -import BoxRounded from 'components/ui/BoxRounded'; -import useAvailableFundsGlm from 'hooks/helpers/useAvailableFundsGlm'; -import useDepositValue from 'hooks/queries/useDepositValue'; -import getFormattedGlmValue from 'utils/getFormattedGlmValue'; - -import styles from './EarnGlmLockBudgetBox.module.scss'; -import EarnGlmLockBudgetBoxProps from './types'; - -const EarnGlmLockBudgetBox: FC = ({ - className, - isWalletBalanceError, - isCurrentlyLockedError, -}) => { - const { data: depositsValue, isFetching: isFetchingDepositValue } = useDepositValue(); - const { data: availableFundsGlm, isFetched: isFetchedAvailableFundsGlm } = useAvailableFundsGlm(); - - const { t } = useTranslation('translation', { - keyPrefix: 'components.dedicated.budgetBox', - }); - - const depositsValueString = useMemo( - () => getFormattedGlmValue({ value: depositsValue || BigInt(0) }).fullString, - [depositsValue], - ); - - const availableFundsGlmString = getFormattedGlmValue({ - value: BigInt(availableFundsGlm ? availableFundsGlm!.value : 0), - }).fullString; - - return ( - -
-
{t('currentlyLocked')}
- {isFetchingDepositValue ? ( -
- ) : ( -
- {depositsValueString} -
- )} -
-
-
{t('walletBalance')}
- {!isFetchedAvailableFundsGlm ? ( -
- ) : ( -
- {availableFundsGlmString} -
- )} -
- - ); -}; - -export default EarnGlmLockBudgetBox; diff --git a/client/src/components/Earn/EarnGlmLock/EarnGlmLockBudgetBox/index.tsx b/client/src/components/Earn/EarnGlmLock/EarnGlmLockBudgetBox/index.tsx deleted file mode 100644 index 07c38db419..0000000000 --- a/client/src/components/Earn/EarnGlmLock/EarnGlmLockBudgetBox/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line no-restricted-exports -export { default } from './EarnGlmLockBudgetBox'; diff --git a/client/src/components/Earn/EarnGlmLock/EarnGlmLockBudgetBox/types.ts b/client/src/components/Earn/EarnGlmLock/EarnGlmLockBudgetBox/types.ts deleted file mode 100644 index 74d224f84c..0000000000 --- a/client/src/components/Earn/EarnGlmLock/EarnGlmLockBudgetBox/types.ts +++ /dev/null @@ -1,5 +0,0 @@ -export default interface EarnGlmLockBudgetBoxProps { - className?: string; - isCurrentlyLockedError?: boolean; - isWalletBalanceError?: boolean; -} diff --git a/client/src/components/Earn/EarnGlmLock/EarnGlmLockNotification/EarnGlmLockNotification.module.scss b/client/src/components/Earn/EarnGlmLock/EarnGlmLockNotification/EarnGlmLockNotification.module.scss deleted file mode 100644 index 8c77ef5984..0000000000 --- a/client/src/components/Earn/EarnGlmLock/EarnGlmLockNotification/EarnGlmLockNotification.module.scss +++ /dev/null @@ -1,35 +0,0 @@ -.notification { - display: flex; - align-items: center; -} - -.info { - height: 11.2rem; - display: flex; - flex-direction: column; - justify-content: center; - align-items: start; - font-size: $font-size-12; - line-height: 2rem; - margin-left: 2rem; - - .label { - color: $color-octant-dark; - } - - .text { - color: $color-octant-grey5; - text-align: left; - - .link { - font-size: $font-size-12; - min-height: 2rem; - font-weight: $font-weight-semibold; - - &:hover { - cursor: pointer; - transform: none; - } - } - } -} diff --git a/client/src/components/Earn/EarnGlmLock/EarnGlmLockNotification/EarnGlmLockNotification.tsx b/client/src/components/Earn/EarnGlmLock/EarnGlmLockNotification/EarnGlmLockNotification.tsx deleted file mode 100644 index e32df3f928..0000000000 --- a/client/src/components/Earn/EarnGlmLock/EarnGlmLockNotification/EarnGlmLockNotification.tsx +++ /dev/null @@ -1,96 +0,0 @@ -import React, { FC, useMemo } from 'react'; -import { Trans, useTranslation } from 'react-i18next'; - -import BoxRounded from 'components/ui/BoxRounded'; -import Button from 'components/ui/Button'; -import Svg from 'components/ui/Svg'; -import networkConfig from 'constants/networkConfig'; -import { arrowTopRight, checkMark, notificationIconWarning } from 'svg/misc'; - -import styles from './EarnGlmLockNotification.module.scss'; -import EarnGlmLockNotificationProps from './types'; - -const ButtonLinkWithIcon: FC<{ children?: React.ReactNode; transactionHash: string }> = ({ - children, - transactionHash, -}) => { - return ( - - ); -}; - -const EarnGlmLockNotification: FC = ({ - className, - isLockingApproved, - type, - transactionHash, - currentMode, -}) => { - const keyPrefix = 'components.dedicated.glmLock.glmLockNotification'; - - const { t } = useTranslation('translation', { - keyPrefix, - }); - - const label = useMemo(() => { - if (type === 'info' && currentMode === 'lock' && !isLockingApproved) { - return t('info.lock.notApproved.label'); - } - if (type === 'success' && currentMode === 'lock') { - return t('success.labelLocked'); - } - if (type === 'success' && currentMode === 'unlock') { - return t('success.labelUnlocked'); - } - }, [t, currentMode, type, isLockingApproved]); - - const text = useMemo(() => { - if (type === 'success') { - return `${keyPrefix}.success.text`; - } - if (type === 'info' && currentMode === 'lock' && !isLockingApproved) { - return `${keyPrefix}.info.lock.notApproved.text`; - } - }, [type, currentMode, isLockingApproved]); - - return ( - -
- -
- {label &&
{label}
} - {text && ( -
- ] - : undefined - } - i18nKey={text} - /> -
- )} -
-
-
- ); -}; - -export default EarnGlmLockNotification; diff --git a/client/src/components/Earn/EarnGlmLock/EarnGlmLockNotification/index.tsx b/client/src/components/Earn/EarnGlmLock/EarnGlmLockNotification/index.tsx deleted file mode 100644 index 6f753d1088..0000000000 --- a/client/src/components/Earn/EarnGlmLock/EarnGlmLockNotification/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line no-restricted-exports -export { default } from './EarnGlmLockNotification'; diff --git a/client/src/components/Earn/EarnGlmLock/EarnGlmLockNotification/types.ts b/client/src/components/Earn/EarnGlmLock/EarnGlmLockNotification/types.ts deleted file mode 100644 index 44c72a0b37..0000000000 --- a/client/src/components/Earn/EarnGlmLock/EarnGlmLockNotification/types.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { CurrentMode } from 'components/Earn/EarnGlmLock/types'; - -export default interface EarnGlmLockNotificationProps { - className?: string; - currentMode: CurrentMode; - isLockingApproved: boolean; - transactionHash?: string; - type: 'success' | 'info'; -} diff --git a/client/src/components/Earn/EarnGlmLock/EarnGlmLockStepper/EarnGlmLockStepper.tsx b/client/src/components/Earn/EarnGlmLock/EarnGlmLockStepper/EarnGlmLockStepper.tsx deleted file mode 100644 index e2c30eb9dd..0000000000 --- a/client/src/components/Earn/EarnGlmLock/EarnGlmLockStepper/EarnGlmLockStepper.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { useFormikContext } from 'formik'; -import React, { FC } from 'react'; -import { useTranslation } from 'react-i18next'; - -import { FormFields } from 'components/Earn/EarnGlmLock/types'; -import BoxRounded from 'components/ui/BoxRounded'; -import ProgressStepper from 'components/ui/ProgressStepper'; - -import EarnGlmLockStepperProps from './types'; - -const EarnGlmLockStepper: FC = ({ currentMode, step, className }) => { - const { t, i18n } = useTranslation('translation', { - keyPrefix: 'components.dedicated.glmLock', - }); - const { isValid } = useFormikContext(); - - return ( - - - - ); -}; - -export default EarnGlmLockStepper; diff --git a/client/src/components/Earn/EarnGlmLock/EarnGlmLockStepper/index.tsx b/client/src/components/Earn/EarnGlmLock/EarnGlmLockStepper/index.tsx deleted file mode 100644 index 002295c96d..0000000000 --- a/client/src/components/Earn/EarnGlmLock/EarnGlmLockStepper/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line no-restricted-exports -export { default } from './EarnGlmLockStepper'; diff --git a/client/src/components/Earn/EarnGlmLock/EarnGlmLockStepper/types.ts b/client/src/components/Earn/EarnGlmLock/EarnGlmLockStepper/types.ts deleted file mode 100644 index 5eee7e952c..0000000000 --- a/client/src/components/Earn/EarnGlmLock/EarnGlmLockStepper/types.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { CurrentMode } from '../types'; - -export default interface EarnGlmLockStepperProps { - className?: string; - currentMode: CurrentMode; - step: 1 | 2 | 3; -} diff --git a/client/src/components/Earn/EarnGlmLock/EarnGlmLockTabs/EarnGlmLockTabs.module.scss b/client/src/components/Earn/EarnGlmLock/EarnGlmLockTabs/EarnGlmLockTabs.module.scss deleted file mode 100644 index 067a9b9fbe..0000000000 --- a/client/src/components/Earn/EarnGlmLock/EarnGlmLockTabs/EarnGlmLockTabs.module.scss +++ /dev/null @@ -1,52 +0,0 @@ -.box { - position: relative; - padding: 1.8rem 2.4rem 2.4rem; - - .max { - position: absolute; - font-size: $font-size-14; - line-height: 2rem; - top: 1.8rem; - right: 2.4rem; - color: $color-octant-green; - cursor: pointer; - - &.isDisabled { - opacity: 0.3; - cursor: default; - } - - &:not(.isDisabled):hover { - color: $color-octant-green2; - } - } -} - -.inputsLabel { - display: flex; - justify-content: space-between; - - .inputsLabelBalance { - display: flex; - - .availableValue { - margin: 0 0.2rem; - - &.dontHaveEnough { - color: $color-octant-orange; - } - } - - .lockedValue { - margin-right: 0.2rem; - - &.cantUnlock { - color: $color-octant-orange; - } - } - } -} - -.button { - width: 100%; -} diff --git a/client/src/components/Earn/EarnGlmLock/EarnGlmLockTabs/EarnGlmLockTabs.tsx b/client/src/components/Earn/EarnGlmLock/EarnGlmLockTabs/EarnGlmLockTabs.tsx deleted file mode 100644 index 02b418d638..0000000000 --- a/client/src/components/Earn/EarnGlmLock/EarnGlmLockTabs/EarnGlmLockTabs.tsx +++ /dev/null @@ -1,177 +0,0 @@ -import cx from 'classnames'; -import { useFormikContext } from 'formik'; -import React, { FC, useMemo, useRef } from 'react'; -import { useTranslation } from 'react-i18next'; - -import EarnGlmLockTabsInputs from 'components/Earn/EarnGlmLock/EarnGlmLockTabsInputs'; -import { FormFields } from 'components/Earn/EarnGlmLock/types'; -import BoxRounded from 'components/ui/BoxRounded'; -import Button from 'components/ui/Button'; -import ButtonProps from 'components/ui/Button/types'; -import useAvailableFundsGlm from 'hooks/helpers/useAvailableFundsGlm'; -import useDepositValue from 'hooks/queries/useDepositValue'; -import { formatUnitsBigInt } from 'utils/formatUnitsBigInt'; -import getFormattedGlmValue from 'utils/getFormattedGlmValue'; -import { parseUnitsBigInt } from 'utils/parseUnitsBigInt'; - -import styles from './EarnGlmLockTabs.module.scss'; -import EarnGlmLockTabsProps from './types'; - -const EarnGlmLockTabs: FC = ({ - buttonUseMaxRef, - className, - currentMode, - isLoading, - onClose, - onInputsFocusChange, - onReset, - setFieldValue, - setValueToDepose, - showBalances, - step, -}) => { - const { i18n, t } = useTranslation('translation', { - keyPrefix: 'components.dedicated.glmLock', - }); - const formik = useFormikContext(); - const inputRef = useRef(null); - - const { data: availableFundsGlm } = useAvailableFundsGlm(); - const { data: depositsValue } = useDepositValue(); - - const isMaxDisabled = isLoading || step > 1; - - const onSetValue = (value: string): void => { - formik.setFieldValue('valueToDeposeOrWithdraw', value); - setValueToDepose(value ? parseUnitsBigInt(value) : BigInt(0)); - }; - - const onMax = () => { - if (isMaxDisabled || depositsValue === undefined || !availableFundsGlm) { - return; - } - const value = - currentMode === 'lock' - ? formatUnitsBigInt(BigInt(availableFundsGlm.value)) - : formatUnitsBigInt(depositsValue); - - onSetValue(value); - inputRef.current?.focus(); - }; - - const buttonCtaProps: ButtonProps = - step === 3 - ? { - onClick: onClose, - type: 'button', - } - : { - type: 'submit', - }; - - const buttonLabel = useMemo(() => { - if (isLoading) { - return i18n.t('common.waitingForConfirmation'); - } - if (step === 3) { - return i18n.t('common.close'); - } - if (currentMode === 'unlock') { - return t('unlock'); - } - return t('lock'); - }, [currentMode, step, t, isLoading, i18n]); - - const isButtonDisabled = - !formik.isValid || parseUnitsBigInt(formik.values.valueToDeposeOrWithdraw || '0') === 0n; - - return ( - onReset({ newMode: 'lock', setFieldValue }), - title: t('lock'), - }, - { - isActive: currentMode === 'unlock', - isDisabled: isLoading, - onClick: () => onReset({ newMode: 'unlock', setFieldValue }), - title: t('unlock'), - }, - ]} - > - - - {t(currentMode === 'lock' ? 'glmLockTabs.amountToLock' : 'glmLockTabs.amountToUnlock')} - {showBalances && ( -
-
- {getFormattedGlmValue({ value: depositsValue || BigInt(0) }).value} -
- {t('glmLockTabs.locked')} -
- { - getFormattedGlmValue({ - value: BigInt(availableFundsGlm ? availableFundsGlm?.value : 0), - }).value - } -
- {i18n.t('common.available')} -
- )} -
- } - mode={currentMode} - onChange={onSetValue} - onInputsFocusChange={onInputsFocusChange} - /> -
- ); -}; - -export default EarnWithdrawEth; diff --git a/client/src/components/Earn/EarnWithdrawEth/index.tsx b/client/src/components/Earn/EarnWithdrawEth/index.tsx deleted file mode 100644 index 0939f1d9b8..0000000000 --- a/client/src/components/Earn/EarnWithdrawEth/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line no-restricted-exports -export { default } from './EarnWithdrawEth'; diff --git a/client/src/components/Earn/EarnWithdrawEth/types.ts b/client/src/components/Earn/EarnWithdrawEth/types.ts deleted file mode 100644 index e332c4c258..0000000000 --- a/client/src/components/Earn/EarnWithdrawEth/types.ts +++ /dev/null @@ -1,3 +0,0 @@ -export default interface EarnWithdrawEthProps { - onCloseModal: () => void; -} diff --git a/client/src/components/Earn/ModalEarnGlmLock/ModalEarnGlmLock.tsx b/client/src/components/Earn/ModalEarnGlmLock/ModalEarnGlmLock.tsx deleted file mode 100644 index 2f8e833155..0000000000 --- a/client/src/components/Earn/ModalEarnGlmLock/ModalEarnGlmLock.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import React, { FC, useState } from 'react'; -import { useTranslation } from 'react-i18next'; - -import EarnGlmLock from 'components/Earn/EarnGlmLock'; -import { CurrentMode } from 'components/Earn/EarnGlmLock/types'; -import Modal from 'components/ui/Modal'; - -import ModalEarnGlmLockProps from './types'; - -const ModalEarnGlmLock: FC = ({ modalProps }) => { - const { t, i18n } = useTranslation('translation', { - keyPrefix: 'components.dedicated.modalGlmLock', - }); - const [currentMode, setCurrentMode] = useState('lock'); - - const modalHeader = currentMode === 'lock' ? i18n.t('common.lockGlm') : t('unlockGLM'); - - return ( - - - - ); -}; - -export default ModalEarnGlmLock; diff --git a/client/src/components/Earn/ModalEarnGlmLock/index.tsx b/client/src/components/Earn/ModalEarnGlmLock/index.tsx deleted file mode 100644 index 252540a7ba..0000000000 --- a/client/src/components/Earn/ModalEarnGlmLock/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line no-restricted-exports -export { default } from './ModalEarnGlmLock'; diff --git a/client/src/components/Earn/ModalEarnGlmLock/types.ts b/client/src/components/Earn/ModalEarnGlmLock/types.ts deleted file mode 100644 index a282b96bad..0000000000 --- a/client/src/components/Earn/ModalEarnGlmLock/types.ts +++ /dev/null @@ -1,5 +0,0 @@ -import ModalProps from 'components/ui/Modal/types'; - -export default interface ModalEarnGlmLockProps { - modalProps: Omit; -} diff --git a/client/src/components/Earn/ModalEarnRewardsCalculator/ModalEarnRewardsCalculator.module.scss b/client/src/components/Earn/ModalEarnRewardsCalculator/ModalEarnRewardsCalculator.module.scss deleted file mode 100644 index f73dec9c84..0000000000 --- a/client/src/components/Earn/ModalEarnRewardsCalculator/ModalEarnRewardsCalculator.module.scss +++ /dev/null @@ -1,21 +0,0 @@ -.root { - .header { - display: flex; - align-items: center; - overflow: visible; - } -} - -.tooltip { - margin-left: 0.8rem; - position: relative; - - .tooltipContainer { - top: 3.6rem; - left: -16.7rem; - } -} - -.tooltipWrapper { - position: relative; -} diff --git a/client/src/components/Earn/ModalEarnRewardsCalculator/ModalEarnRewardsCalculator.tsx b/client/src/components/Earn/ModalEarnRewardsCalculator/ModalEarnRewardsCalculator.tsx deleted file mode 100644 index aa83945606..0000000000 --- a/client/src/components/Earn/ModalEarnRewardsCalculator/ModalEarnRewardsCalculator.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import React, { FC } from 'react'; -import { useTranslation } from 'react-i18next'; - -import EarnRewardsCalculator from 'components/Earn/EarnRewardsCalculator'; -import Modal from 'components/ui/Modal'; -import Svg from 'components/ui/Svg'; -import Tooltip from 'components/ui/Tooltip'; -import { questionMark } from 'svg/misc'; - -import styles from './ModalEarnRewardsCalculator.module.scss'; -import ModalEarnRewardsCalculatorProps from './types'; - -const ModalEarnRewardsCalculator: FC = ({ modalProps }) => { - const { i18n, t } = useTranslation('translation', { - keyPrefix: 'components.dedicated.rewardsCalculator', - }); - - return ( - -
{i18n.t('common.estimateRewards')}
- - - - - } - headerClassName={styles.header} - isOpen={modalProps.isOpen} - onClosePanel={modalProps.onClosePanel} - > - -
- ); -}; - -export default ModalEarnRewardsCalculator; diff --git a/client/src/components/Earn/ModalEarnRewardsCalculator/index.tsx b/client/src/components/Earn/ModalEarnRewardsCalculator/index.tsx deleted file mode 100644 index 975cda7194..0000000000 --- a/client/src/components/Earn/ModalEarnRewardsCalculator/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line no-restricted-exports -export { default } from './ModalEarnRewardsCalculator'; diff --git a/client/src/components/Earn/ModalEarnRewardsCalculator/types.ts b/client/src/components/Earn/ModalEarnRewardsCalculator/types.ts deleted file mode 100644 index 04220a105c..0000000000 --- a/client/src/components/Earn/ModalEarnRewardsCalculator/types.ts +++ /dev/null @@ -1,5 +0,0 @@ -import ModalProps from 'components/ui/Modal/types'; - -export default interface ModalEarnRewardsCalculatorProps { - modalProps: Omit; -} diff --git a/client/src/components/Earn/ModalEarnWithdrawEth/ModalEarnWithdrawEth.tsx b/client/src/components/Earn/ModalEarnWithdrawEth/ModalEarnWithdrawEth.tsx deleted file mode 100644 index 1004f04d8d..0000000000 --- a/client/src/components/Earn/ModalEarnWithdrawEth/ModalEarnWithdrawEth.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import React, { FC } from 'react'; -import { useTranslation } from 'react-i18next'; - -import EarnWithdrawEth from 'components/Earn/EarnWithdrawEth'; -import Modal from 'components/ui/Modal'; - -import ModalEarnWithdrawingProps from './types'; - -const ModalEarnWithdrawEth: FC = ({ modalProps }) => { - const { t } = useTranslation('translation', { - keyPrefix: 'components.dedicated.modalWithdrawEth', - }); - - return ( - - - - ); -}; - -export default ModalEarnWithdrawEth; diff --git a/client/src/components/Earn/ModalEarnWithdrawEth/index.tsx b/client/src/components/Earn/ModalEarnWithdrawEth/index.tsx deleted file mode 100644 index 1744f844c5..0000000000 --- a/client/src/components/Earn/ModalEarnWithdrawEth/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line no-restricted-exports -export { default } from './ModalEarnWithdrawEth'; diff --git a/client/src/components/Earn/ModalEarnWithdrawEth/types.ts b/client/src/components/Earn/ModalEarnWithdrawEth/types.ts deleted file mode 100644 index a455783d0b..0000000000 --- a/client/src/components/Earn/ModalEarnWithdrawEth/types.ts +++ /dev/null @@ -1,5 +0,0 @@ -import ModalProps from 'components/ui/Modal/types'; - -export default interface ModaEarnlWithdrawEthProps { - modalProps: Omit; -} diff --git a/client/src/components/Home/HomeGridCurrentGlmLock/HomeGridCurrentGlmLock.tsx b/client/src/components/Home/HomeGridCurrentGlmLock/HomeGridCurrentGlmLock.tsx index 694b2eab23..f81599ea23 100644 --- a/client/src/components/Home/HomeGridCurrentGlmLock/HomeGridCurrentGlmLock.tsx +++ b/client/src/components/Home/HomeGridCurrentGlmLock/HomeGridCurrentGlmLock.tsx @@ -77,7 +77,7 @@ const HomeGridCurrentGlmLock: FC = ({ className }) valueCrypto: estimatedEffectiveDeposit, }, isDisabled: isPreLaunch && !isConnected, - label: isProjectAdminMode ? t('pending') : t('effective'), + label: isProjectAdminMode ? i18n.t('common.pending') : t('effective'), tooltipProps: { dataTest: 'TooltipEffectiveLockedBalance', position: 'bottom-right', diff --git a/client/src/components/Home/HomeGridDonations/HomeGridDonations.tsx b/client/src/components/Home/HomeGridDonations/HomeGridDonations.tsx index 6dcf091507..93b09933b6 100644 --- a/client/src/components/Home/HomeGridDonations/HomeGridDonations.tsx +++ b/client/src/components/Home/HomeGridDonations/HomeGridDonations.tsx @@ -18,7 +18,7 @@ import HomeGridDonationsProps from './types'; import { getReducedUserAllocationsAllEpochs } from './utils'; const HomeGridDonations: FC = ({ className }) => { - const { t } = useTranslation('translation', { + const { i18n, t } = useTranslation('translation', { keyPrefix: 'components.home.homeGridDonations', }); const { isConnected } = useAccount(); @@ -50,7 +50,9 @@ const HomeGridDonations: FC = ({ className }) => { showTitleDivider={!areAllocationsEmpty} title={
- {!isDecisionWindowOpen && !areAllocationsEmpty ? t('donationHistory') : t('donations')} + {!isDecisionWindowOpen && !areAllocationsEmpty + ? t('donationHistory') + : i18n.t('common.donations')} {isDecisionWindowOpen && userAllocations?.elements !== undefined && (
{userAllocations?.elements?.length}
)} @@ -67,7 +69,7 @@ const HomeGridDonations: FC = ({ className }) => { }} variant="cta" > - {t('edit')} + {i18n.t('common.edit')} ) : null } diff --git a/client/src/components/Home/HomeGridEpochResults/EpochResultsDetails/EpochResultsDetails.tsx b/client/src/components/Home/HomeGridEpochResults/EpochResultsDetails/EpochResultsDetails.tsx index 1f44449640..000e7ab95b 100644 --- a/client/src/components/Home/HomeGridEpochResults/EpochResultsDetails/EpochResultsDetails.tsx +++ b/client/src/components/Home/HomeGridEpochResults/EpochResultsDetails/EpochResultsDetails.tsx @@ -11,7 +11,7 @@ import styles from './EpochResultsDetails.module.scss'; import EpochResultsDetailsProps from './types'; const EpochResultsDetails: FC = ({ details }) => { - const { t } = useTranslation('translation', { + const { i18n, t } = useTranslation('translation', { keyPrefix: 'components.home.homeGridEpochResults', }); const { isMobile } = useMediaQuery(); @@ -60,13 +60,13 @@ const EpochResultsDetails: FC = ({ details }) => { <>
{details.name}
- {isMobile ? t('donationsShort') : t('donations')} {donationsToDisplay} + {isMobile ? t('donationsShort') : i18n.t('common.donations')} {donationsToDisplay}
- {isMobile ? t('matchingShort') : t('matching')} {matchingToDisplay} + {isMobile ? t('matchingShort') : i18n.t('common.matching')} {matchingToDisplay}
- {isMobile ? t('totalShort') : t('total')} {totalToDisplay} + {isMobile ? t('totalShort') : i18n.t('common.total')} {totalToDisplay}
{!isMobile && (
- {/* {isHeaderVisible && ( - - {showHeaderBlur &&
} -
-
-
- - {networkConfig.isTestnet && ( -
-
{networkConfig.name}
-
- )} -
-
- {isConnected && address ? ( -
isUserTOSAccepted && setIsWalletModalOpen(true)} - > -
-
- {(isProjectAdminMode || isPatronMode) && ( -
- {isProjectAdminMode ? t('admin') : t('patron')} -
- )} - -
- {truncatedEthAddress} -
-
- {!!currentEpoch && - currentEpoch > 1 && - (showAllocationPeriod ? ( -
- , - ]} - i18nKey={ - isDecisionWindowOpen - ? 'layout.main.allocationEndsIn' - : 'layout.main.allocationStartsIn' - } - values={{ currentPeriod }} - /> -
- ) : ( -
{individualRewardText}
- ))} -
-
- ) : ( -
-
-
- - )} */}
= ({ !!navigationBottomSuffix && styles.isNavigationBottomSuffix, classNameBody, )} - data-test="MainLayout__body" + data-test="Layout__body" id={LAYOUT_BODY_ID} > - {isLoading ? : children} + {isLoading ? : children}
{!isDesktop && isNavigationVisible && ( diff --git a/client/src/components/shared/Layout/LayoutNavbar/LayoutNavbar.module.scss b/client/src/components/shared/Layout/LayoutNavbar/LayoutNavbar.module.scss index bbd81d94d9..27a8b07c07 100644 --- a/client/src/components/shared/Layout/LayoutNavbar/LayoutNavbar.module.scss +++ b/client/src/components/shared/Layout/LayoutNavbar/LayoutNavbar.module.scss @@ -4,7 +4,8 @@ } .navigationWrapper { - @include layoutFloatingElementWidth(); + width: 100%; + min-width: 39rem; display: flex; justify-content: center; position: fixed; diff --git a/client/src/components/shared/ViewTitle/ViewTitle.module.scss b/client/src/components/shared/ViewTitle/ViewTitle.module.scss index b175639f9d..aa31894687 100644 --- a/client/src/components/shared/ViewTitle/ViewTitle.module.scss +++ b/client/src/components/shared/ViewTitle/ViewTitle.module.scss @@ -11,4 +11,12 @@ height: 14.4rem; font-size: $font-size-32; } + + @media #{$desktop-up} { + font-size: $font-size-40; + } + + @media #{$large-desktop-up} { + font-size: $font-size-48; + } } diff --git a/client/src/constants/store.ts b/client/src/constants/store.ts deleted file mode 100644 index c944d141ba..0000000000 --- a/client/src/constants/store.ts +++ /dev/null @@ -1 +0,0 @@ -export const REQUEST_ACTIONS_SPACER = '_'; diff --git a/client/src/hooks/events/useAllocate.ts b/client/src/hooks/events/useAllocate.ts index 8b06a33a4b..1bda81b8b6 100644 --- a/client/src/hooks/events/useAllocate.ts +++ b/client/src/hooks/events/useAllocate.ts @@ -4,11 +4,11 @@ import { useAccount, useSignTypedData } from 'wagmi'; import { SignatureOpType, apiPostPendingMultisigSignatures } from 'api/calls/multisigSignatures'; import { apiGetSafeMessages } from 'api/calls/safeMessages'; import { handleError } from 'api/errorMessages'; +import { AllocationValues } from 'components/Allocation/types'; import networkConfig from 'constants/networkConfig'; import useIsContract from 'hooks/queries/useIsContract'; import { getAllocationsMapped } from 'hooks/utils/utils'; import { WebsocketEmitEvent } from 'types/websocketEvents'; -import { AllocationValues } from 'views/AllocationView/types'; const websocketService = () => import('services/websocketService'); diff --git a/client/src/hooks/helpers/useNavigationTabs.tsx b/client/src/hooks/helpers/useNavigationTabs.tsx index 75be2ab61e..2ba1a58530 100644 --- a/client/src/hooks/helpers/useNavigationTabs.tsx +++ b/client/src/hooks/helpers/useNavigationTabs.tsx @@ -22,7 +22,7 @@ const useNavigationTabs = (isTopBar?: boolean): NavigationTab[] => { // TODO: patron mode support -> https://linear.app/golemfoundation/issue/OCT-1893/layout-patron-mode // const { data: isPatronMode } = useIsPatronMode(); - const { t } = useTranslation('translation', { keyPrefix: 'layout.navigationTabs' }); + const { i18n, t } = useTranslation('translation', { keyPrefix: 'layout.navigationTabs' }); const { isConnected } = useAccount(); const { data: isUserTOSAccepted } = useUserTOS(); const { pathname } = useLocation(); @@ -45,7 +45,7 @@ const useNavigationTabs = (isTopBar?: boolean): NavigationTab[] => { ROOT_ROUTES.projects.absolute === pathname || pathname.includes(`${ROOT_ROUTES.project.absolute}/`), key: 'projects', - label: t('projects'), + label: i18n.t('common.projects'), to: ROOT_ROUTES.projects.absolute, }, { diff --git a/client/src/hooks/mutations/useAllocateSimulate.ts b/client/src/hooks/mutations/useAllocateSimulate.ts index 0922389fa0..f906021714 100644 --- a/client/src/hooks/mutations/useAllocateSimulate.ts +++ b/client/src/hooks/mutations/useAllocateSimulate.ts @@ -3,8 +3,8 @@ import { useRef } from 'react'; import { useAccount } from 'wagmi'; import { apiPostAllocateLeverage, ApiPostAllocateLeverageResponse } from 'api/calls/allocate'; +import { AllocationValues } from 'components/Allocation/types'; import { getAllocationsMapped } from 'hooks/utils/utils'; -import { AllocationValues } from 'views/AllocationView/types'; export type AllocateSimulate = Omit & { threshold: bigint; diff --git a/client/src/hooks/utils/utils.ts b/client/src/hooks/utils/utils.ts index dbe8cc5282..06761e9792 100644 --- a/client/src/hooks/utils/utils.ts +++ b/client/src/hooks/utils/utils.ts @@ -1,6 +1,6 @@ +import { AllocationValues } from 'components/Allocation/types'; import { formatUnitsBigInt } from 'utils/formatUnitsBigInt'; import { parseUnitsBigInt } from 'utils/parseUnitsBigInt'; -import { AllocationValues } from 'views/AllocationView/types'; export function getAllocationsMapped( allocationValues: AllocationValues, diff --git a/client/src/locales/en/translation.json b/client/src/locales/en/translation.json index 86be90a9ad..ef1f375955 100644 --- a/client/src/locales/en/translation.json +++ b/client/src/locales/en/translation.json @@ -25,8 +25,6 @@ }, "common": { "availableNow": "Available now", - "calculateRewards": "Calculate rewards", - "estimateRewards": "Estimate rewards", "close": "Close", "copied": "Copied", "copy": "Copy link", @@ -34,17 +32,11 @@ "donations": "Donations", "done": "Done", "gettingStarted": "Getting started", - "history": "History", "lockGlm": "Lock GLM", - "octantTips": "Octant tips", "personal": "Personal", "personalAllocation": "Personal allocation", "projectForbiddenOperation": "As a project you are not able to proceed with this operation", "rewards": "Rewards {{rewards}}", - "thresholdDataUnavailable": { - "desktop": "Threshold data unavailable", - "mobile": "No threshold data" - }, "matchFunding": "Match funding", "valueCantBeEmpty": "Value can't be empty", "lessThan1m": "less than 1m", @@ -52,7 +44,15 @@ "projects": "Projects", "available": "Available", "totalDonated": "Total donated", - "waitingForConfirmation": "Waiting for confirmation" + "waitingForConfirmation": "Waiting for confirmation", + "pending": "Pending", + "amount": "Amount", + "edit": "Edit", + "donated": "Donated", + "lockedGLM": "Locked GLM", + "unlockedGLM": "Unlocked GLM", + "total": "Total", + "matching": "Matching" }, "components": { "allocation": { @@ -93,9 +93,6 @@ "mobile": "You will receive only 20% of the max. match funding. Visit <0>Settings to recalculate before you allocate" } } - }, - "modalAllocationValuesEdit": { - "header": "Edit {{allocation}}" } }, "shared": { @@ -107,10 +104,8 @@ "homeRewards": { "currentRewards": "Current rewards", "currentDonations": "Current donations", - "donations": "Donations", "totalRewards": "Total rewards", "currentMatchFunding": "Current match funding", - "matchFunding": "Match funding", "rewardsRate": "Rewards rate", "epochTotalMatchFunding": "Epoch total match funding", "epochMF": "Epoch MF" @@ -120,33 +115,20 @@ "yourFunds": "Your funds", "editLockedGLM": "Edit Locked GLM", "effective": "Effective", - "pending": "Pending", - "lockedBalance": "Locked balance", "tooltipText": "Effective lock (EL) is the part of your locked GLM that is currently earning rewards. Equal to your current balance, if that is unchanged during the ongoing epoch. Increase lock & EL increases proportionally to epoch time remaining. Decrease lock & this amount is removed from EL for the epoch.", "modalLockGlm": { "unlockGLM": "Unlock GLM", "lockGlmBudgetBox": { - "approve2Transactions": "Please approve 2 transactions in your wallet. The first (required only once and for locking) allows ERC-20 tokens, and the second locks / unlocks GLM.", "currentlyLocked": "Currently Locked", - "stakeWillUpdate": "Your stake will update when the transaction is confirmed", - "waitingForTransactionHash": "Waiting for transaction hash from Etherscan...", - "walletBalance": "Wallet balance", - "viewOnEtherscan": "View on Etherscan" + "walletBalance": "Wallet balance" } } }, "homeGridPersonalAllocation": { - "personalAllocation": "Personal allocation", - "patronEarnings": "Patron earnings", - "pending": "Pending", "withdrawToWallet": "Withdraw to wallet", "pendingFundsAvailableAfter": "Pending funds available after", - "currentEpoch": "Current epoch", "modalWithdrawEth": { "withdrawETH": "Withdraw ETH", - "withdrawalsDistributedEpoch": "Withdrawals are distributed when Epoch {{currentEpoch}} ends", - "rewardsBudget": "Rewards Budget", - "amount": "Amount", "estimatedGasPrice": "Est. gas price", "withdrawAll": "Withdraw all" } @@ -175,7 +157,6 @@ "calculatingYourUniquenessStep3": "Delegation will not link your addresses or compromise your privacy in any way.

We require proof of uniqueness to defend against sybil attacks as we have switched to a quadratic funding model.

To learn more, check out Gitcoin’s handy guide to <0>scoring 20, for humans." }, "modalCalculatingUQScore": { - "switchAccounts": "Switch accounts", "signMessages": "Sign messages", "switchAccount": "Switch account", "calculatingScore": "Calculating score", @@ -201,20 +182,15 @@ "modalTransactionDetails": { "header": { "allocation": "Epoch {{epoch}} Allocations", - "lock": "Locked GLM", - "unlock": "Unlocked GLM", "withdrawal": "Withdrew ETH" }, "sections": { - "allocationPersonal": "Personal", "allocationProjects": "Projects ({{projectsNumber}})", - "amount": "Amount", "gasPrice": "Gas price", "viewOnEtherscan": "View on Etherscan", "estimatedLeverage": "Est. leverage", "finalMatchFunding": "Final match funding", "when": "When", - "withdrawal": "Withdrew ETH", "matchingFundDonation": "Matching fund donation", "leverageUnknown": "Unknown", "allocationTooltips": { @@ -229,33 +205,24 @@ }, "transactionsListItem": { "allocatedRewards": "Allocated rewards", - "lockedGLM": "Locked GLM", - "unlockedGLM": "Unlocked GLM", - "projects": "Projects", "withdrawnFunds": "Withdrawn funds", "epochDonation": "Epoch {{epoch}} donation" }, "transactionLabel": { "confirmed": "Confirmed", - "pending": "Pending", "pendingMultisig": "Pending multisig" } }, "homeGridDonations": { - "donations": "Donations", "donationHistory": "Donation history", "noDonationsYet": "No donations yet. Lock GLM
and earn rewards to donate", - "noDonationsYetAWOpen": "No donations yet in this
allocation window.", - "edit": "Edit" + "noDonationsYetAWOpen": "No donations yet in this
allocation window." }, "homeGridEpochResults": { "epochResults": "Epoch {{epoch}} results", "epochLive": "Epoch {{epoch}} live", - "donations": "Donations", "donationsShort": "D", - "matching": "Matching", "matchingShort": "M", - "total": "Total", "totalShort": "T", "clickToVisitProject": "Click to visit project" } @@ -282,41 +249,16 @@ "saveToAllocate": "Save to allocate", "saved": "Saved", "removeFromAllocate": "Remove from allocate", - "removed": "Removed", - "donated": "Donated" - }, - "rewardsCalculator": { - "modalHeaderTooltip": "Calculator assumes GLM is locked before allocation window and remains locked until the next window. Estimated match funding is based on 100% of rewards being allocated to projects.", - "estimates": "Estimates", - "enterGLMAmount": "Enter a GLM amount", - "lockForEpoch_one": "Lock for {{count}} epoch", - "lockForEpoch_other": "Lock for {{count}} epochs", - "estimatedRewards": "Estimated rewards", - "uqSelector": { - "header": "UQ Score 20+", - "isUqScoreOver20_true": "Yes", - "isUqScoreOver20_false": "No" - }, - "errors": { - "valueCryptoTooBig": "That isn’t a valid amount" - } - }, - "allocationInfoBoxes": { - "decisionWindowClosed": "The decision window is now closed. Allocating funds is not possible.", - "connectWallet": "In order to manipulate allocation values and vote, please connect your wallet first." + "removed": "Removed" }, "allocationNavigation": { "confirm": "Confirm", - "edit": "Edit", "reset": "Reset", "waiting": "Waiting" }, "allocationRewardsBox": { - "title": "Allocate rewards", - "subtitle": "{{individualReward}} available", "subtitleNoRewards": "No rewards yet", "donate": "Donate", - "donated": "Donated", "donateWithPercentage": "Donate {{percentage}}%", "personalWithPercentage": "Personal {{percentage}}%", "allocated": "Allocated", @@ -326,38 +268,7 @@ "allocationSummary": { "totalImpact": "Total impact", "estimatedLeverage": "Est. leverage", - "confirmYourAllocations": "Confirm your allocations", - "confirmAllocations": "Confirm allocations", - "allocationProjects": "Projects ({{projectsNumber}})", - "tooltip": "<0>Leverage shows the multiplier effect of your donation to the projects you chose. Note that leverage will fluctuate significantly and might be very different at the end of the allocation window." - }, - "allocationInputsRewardsAvailable": { - "resetAllocations": "Reset allocations to manually edit this project", - "valueExceeded": "You can't allocate more than", - "rewardsAvailable": "Rewards available" - }, - "boxGlmLock": { - "current": "Current", - "editLockedGLM": "Edit Locked GLM", - "effective": "Effective", - "lockedBalance": "Locked balance", - "tooltipText": "Effective lock (EL) is the part of your locked GLM that is currently earning rewards. Equal to your current balance, if that is unchanged during the ongoing epoch. Increase lock & EL increases proportionally to epoch time remaining. Decrease lock & this amount is removed from EL for the epoch." - }, - "boxPersonalAllocation": { - "patronEarnings": "Patron earnings", - "pending": "Pending", - "withdrawToWallet": "Withdraw to wallet", - "pendingFundsAvailableAfter": "Pending funds available after", - "allTime": "All time", - "currentEpoch": "Current epoch" - }, - "budgetBox": { - "approve2Transactions": "Please approve 2 transactions in your wallet. The first (required only once and for locking) allows ERC-20 tokens, and the second locks / unlocks GLM.", - "currentlyLocked": "Currently Locked", - "stakeWillUpdate": "Your stake will update when the transaction is confirmed", - "waitingForTransactionHash": "Waiting for transaction hash from Etherscan...", - "walletBalance": "Wallet balance", - "viewOnEtherscan": "View on Etherscan" + "allocationProjects": "Projects ({{projectsNumber}})" }, "connectWallet": { "browserWallet": "Browser wallet", @@ -365,14 +276,6 @@ "walletConnect": "WalletConnect", "ledgerConnect": "Ledger" }, - "donationEstimateBox": { - "donationEstimate": "Donation estimate", - "showDetails": "Show details", - "hideDetails": "Hide details", - "totalDonationEstimate": "Total donation estimate", - "yourTotal": "Your total", - "matchFundingEstimate": "Match Funding estimate" - }, "donors": { "donationsNotEnabled": "Donations are not enabled in this epoch", "noDonationsYet": "No donations yet", @@ -406,22 +309,9 @@ } } }, - "metricsTimeSection": { - "epochEndsIn": "Epoch {{currentEpoch}} ends in", - "epochAllocationEndsIn": "Epoch {{currentEpoch}} Allocation ends in" - }, - "modalAllocationEditSelection": { - "uncheckToRemove": "Uncheck to remove" - }, "modalConnectWallet": { "connectVia": "Connect via" }, - "modalGlmLock": { - "unlockGLM": "Unlock GLM" - }, - "modalWithdrawEth": { - "withdrawETH": "Withdraw ETH" - }, "projectRewards": { "epoch": "Epoch {{epoch}}", "currentTotal": "Current total", @@ -432,8 +322,7 @@ "didNotReachThreshold": "Did not reach threshold" }, "projectsList": { - "epochArchive": "Epoch {{epoch}} Archive", - "noSearchResults": "No luck, please try again" + "epochArchive": "Epoch {{epoch}} Archive" }, "timeCounter": { "hours": "Hours", @@ -447,20 +336,12 @@ "balances": "Balances", "wallet": "Wallet", "disconnectWallet": "Disconnect Wallet" - }, - "withdrawEth": { - "withdrawalsDistributedEpoch": "Withdrawals are distributed when Epoch {{currentEpoch}} ends", - "rewardsBudget": "Rewards Budget", - "amount": "Amount", - "estimatedGasPrice": "Est. gas price", - "withdrawAll": "Withdraw all" } } }, "layout": { "navigationTabs": { "home": "Home", - "projects": "Projects", "metrics": "Metrics", "settings": "Settings", "allocate": "Allocate" @@ -487,15 +368,6 @@ "farcaster": "Farcaster", "twitterX": "Twitter/X" } - }, - "main": { - "buttonConnect": "Connect", - "noRewardsYet": "No rewards yet", - "loadingRewardBudget": "Loading reward budget...", - "allocationStartsIn": "Allocation starts in <0>{{currentPeriod}}", - "allocationEndsIn": "Allocation ends in <0>{{currentPeriod}}", - "admin": "Admin", - "patron": "Patron" } }, "meta": { @@ -515,89 +387,29 @@ "home": { "title": "Welcome to Epoch {{epoch}}" }, - "earn": { - "tips": { - "withdrawEth": { - "title": "Withdraw ETH", - "text": { - "desktop": "Rewards you allocate to yourself can be withdrawn to your wallet here in the Earn view once the allocation window has closed", - "mobile": "Rewards you allocate to yourself can be withdrawn to your wallet here in the Earn view" - } - }, - "connectWallet": { - "title": { - "desktop": "Connect your wallet", - "mobile": "Connect wallet" - }, - "text": { - "desktop": "Please connect your wallet to get started. You will need it to lock GLM, receive rewards and to prove your uniqueness", - "mobile": "Please connect your wallet to Octant in order to participate in this and future epochs" - } - }, - "lockGlm": { - "title": "Lock GLM to earn", - "text": { - "desktop": "Lock GLM for at least one complete epoch to earn ETH rewards you can donate (and get match funding) or allocate to yourself", - "mobile": "Lock GLM for at least 1 complete epoch to earn ETH rewards you can donate or withdraw" - } - }, - "allocateYourRewards": { - "title": "Allocate your rewards", - "text": { - "desktop": "Unallocated rewards will be swept back for restaking at the end of this allocation window, so don't forget to allocate!", - "mobile": "Unallocated rewards will be swept back for restaking at the end of this allocation window" - } - } - }, - "preLaunch": { - "timerTitle": "Octant launches in" - } - }, "metrics": { + "epoch1Info": "It's Epoch 1, so there are no metrics for the past. It's just a placeholder, please come back in Epoch 2.", "exploreTheData": "Explore the data", "fundingLeaderboard": "Funding leaderboard", - "donors": "Donors", - "personal": "Personal", - "donations": "Donations", - "matchFunding": "Match funding", - "total": "Total", "donatedToProjects": "Donated to projects", "claimedByUsers": "Claimed by users", "open": "Open", - "overview": "Overview", "epoch": "Epoch", "epochAllocationWindow": "Epoch {{epoch}} Allocation Window", "epochAllocation": "E{{epoch}} Allocation", - "yourMetrics": "Your metrics", - "totalRewards": "Total rewards", - "allocationsInEth": "Allocations in ETH", "generalMetrics": "General metrics", - "totalWithdrawals": "Total withdrawals", "donationsVsPersonal": "Donations vs personal", "donationsVsMatchFunding": "Donations vs match funding", "totalMatching": "Total matching", - "matching": "Matching", - "patronModeActive": "Patron mode active", - "patronModeActiveLabel": "{{numberOfEpochs}} epochs", - "donatedAsPatron": "Donated as patron", - "noAllocationsYet": "No allocations yet", "totalProjects": "Total projects", "totalEthStaked": "Total ETH Staked", "totalGlmLocked": "Total GLM Locked", "of1BTotalSupply": "of 1B total supply", - "totalAddresses": "Total addresses", "cumulativeGlmLocked": "Cumulative GLM Locked", "walletsWithGlmLocked": "Wallets with GLM locked", - "epochAllocationStartsIn": "Epoch {{epoch}} allocation starts in", - "epochAllocationEndsIn": "Epoch {{epoch}} allocation ends in", "totalProjectsSinceEpoch0": "{{projectsAmount}} total since E0", - "connectWalletTip": { - "title": "Connect your wallet", - "text": "To see your own personal stats here, <0/>you will need to connect your wallet" - }, "belowThreshold": "Below threshold", "ethBelowThreshold": "ETH Below threshold", - "totalPersonal": "Total personal", "totalDonations": "Total donations", "averageLeverage": "Average leverage", "currentDonors": "Current donors", @@ -605,7 +417,6 @@ "rewardsUnused": "Rewards unused", "unallocatedValue": "Unallocated value", "users": "Users", - "topProjectsByEthRaised": "Top {{numberOfProjects}} projects by ETH raised", "totalUsers": "Total users", "fundsUsage": "Epoch {{epoch}} funds usage ", "epochTotal": "Epoch {{epoch}} Total", @@ -694,55 +505,13 @@ "epoch": "Epoch {{epoch}}", "golemFoundationProject": "A Golem Foundation Project", "poweredByCoinGeckoApi": "Powered by CoinGecko API", - "termsAndConditions": "Terms & Conditions", "chooseDisplayCurrency": "Choose a display currency", "cryptoMainValueDisplay": "Use ETH as main value display", "alwaysShowOnboarding": "Always show onboarding", "alwaysShowOctantTips": "Always show tips", "enablePatronMode": "Enable patron mode", "patronModeTooltip": "Patron mode is for token holders who want to support Octant. It disables allocation to yourself or projects. All rewards go directly to the matching fund with no action required by the patron.", - "octantBuild": "Octant.build", - "userDocs": "User Docs", - "discordCommunity": "Discord Community", - "website": "Website", - "visitWebsite": "Visit the website", - "discord": "Discord", - "joinOurDiscord": "Join our Discord", - "docs": "Docs", - "octantInfo": "Octant is a platform for experiments in decentralized governance that reward participation. Learn more below.", - "recalculatingScore": "Recalculating score", - "calculatingScore": "Calculating score", - "checkingPassportScore": "Checking Passport score", - "finished": "Finished", - "checkingAllowlist": "Checking allowlist", - "checkOutDocs": "Check out the docs", - "yourUniquenessScore": "Your uniqueness score", - "recalculate": "Recalculate", - "delegate": "Delegate", - "whatIsThis": "What is this?", - "scoreTooLow": "Score too low? Visit the Octant Passport dashboard", - "addresses": "addresses", - "calculatingYourUniqueness": "Calculating your uniqueness", - "switchAccounts": "Switch accounts", - "calculatingYourUniquenessStep1": "To prove your uniqueness your need a <0>Gitcoin\u00a0Passport score of 20 or higher.

If you have this your donations will attract the maximum amount of match funding. If not, maximum match funding will be set to 20%.

You can increase your score in a couple of different ways.", - "calculatingYourUniquenessStep2": "You can go to our <0>Passport dashboard and add stamps to the score for your Octant address.

If your Passport score is not on your primary address, you can delegate your score from another address with a 20+ Passport score.

You can only do this once, and it must be done before allocating to benefit the current epoch.", - "calculatingYourUniquenessStep3": "Delegation will not link your addresses or compromise your privacy in any way.

We require proof of uniqueness to defend against sybil attacks as we have switched to a quadratic funding model.

To learn more, check out Gitcoin’s handy guide to <0>scoring 20, for humans.", - "primary": "Primary", - "delegationFailedText": "Delegation failed — your score needs to be 20 or higher. Please try another address", - "delegationMessageToSign": "Delegation of UQ score from {{delegationSecondaryAddress}} to {{delegationPrimaryAddress}}", - "signMessage": "Sign message", - "signMessages": "Sign messages", - "switchAccount": "Switch account", - "toasts": { - "delegationTooManyUniqueAddresses": { - "title": "Too many accounts", - "message": "Please connect up to 10 accounts to check your delegation status" - }, - "unableToDelegateToAddressWithPositiveGLMLock": { - "title": "Address with GLM locked", - "message": "Please delegate to an address without GLM locked" - } - } + "docs": "Docs" }, "syncStatus": { "information": "We're synchronizing things to prepare the
next epoch, so the app will be unavailable
for a little while. Please check back soon." diff --git a/client/src/routes/RootRoutes/RootRoutes.tsx b/client/src/routes/RootRoutes/RootRoutes.tsx index 1d8c822d26..aaff0a0af9 100644 --- a/client/src/routes/RootRoutes/RootRoutes.tsx +++ b/client/src/routes/RootRoutes/RootRoutes.tsx @@ -7,13 +7,13 @@ import useMediaQuery from 'hooks/helpers/useMediaQuery'; import useCurrentEpoch from 'hooks/queries/useCurrentEpoch'; import useIsPatronMode from 'hooks/queries/useIsPatronMode'; import getIsPreLaunch from 'utils/getIsPreLaunch'; -import AllocationView from 'views/AllocationView/AllocationView'; -import HomeView from 'views/HomeView/HomeView'; -import MetricsView from 'views/MetricsView/MetricsView'; -import PlaygroundView from 'views/PlaygroundView/PlaygroundView'; -import ProjectsView from 'views/ProjectsView/ProjectsView'; -import ProjectView from 'views/ProjectView/ProjectView'; -import SettingsView from 'views/SettingsView/SettingsView'; +import AllocationView from 'views/AllocationView'; +import HomeView from 'views/HomeView'; +import MetricsView from 'views/MetricsView'; +import PlaygroundView from 'views/PlaygroundView'; +import ProjectsView from 'views/ProjectsView'; +import ProjectView from 'views/ProjectView'; +import SettingsView from 'views/SettingsView'; import { ROOT_ROUTES } from './routes'; diff --git a/client/src/routes/RootRoutes/routes.ts b/client/src/routes/RootRoutes/routes.ts index 4c6955281d..a8bddb7dc8 100644 --- a/client/src/routes/RootRoutes/routes.ts +++ b/client/src/routes/RootRoutes/routes.ts @@ -5,13 +5,11 @@ const PROJECT_PREFIX = 'project'; export const ROOT_ROUTES = { allocation: getPathObject(ROOT, 'allocation'), - earn: getPathObject(ROOT, 'earn'), home: getPathObject(ROOT, 'home'), metrics: getPathObject(ROOT, 'metrics'), playground: getPathObject(ROOT, 'playground'), project: getPathObject(ROOT, PROJECT_PREFIX), projectWithAddress: getPathObject(ROOT, `${PROJECT_PREFIX}/:epoch/:projectAddress`), projects: getPathObject(ROOT, 'projects'), - settings: getPathObject(ROOT, 'settings'), }; diff --git a/client/src/styles/utils/_keyframes.scss b/client/src/styles/utils/_keyframes.scss index 6503bef4d9..06a5c5b735 100644 --- a/client/src/styles/utils/_keyframes.scss +++ b/client/src/styles/utils/_keyframes.scss @@ -1,53 +1,44 @@ -@keyframes scale1 { +@keyframes wave { 0% { - transform: scale(1); - } - 50% { - transform: scale(1.2); + background-position: -46.8rem 0; } 100% { - transform: scale(1); + background-position: 46.8rem 0; } } -@keyframes spin { +@keyframes horizontal-shaking { 0% { - transform: rotate(0deg); + transform: translateX(0); + } + 25% { + transform: translateX(0.5rem); + } + 50% { + transform: translateX(-0.5rem); + } + 75% { + transform: translateX(0.5rem); } 100% { - transform: rotate(360deg); + transform: translateX(0); } } -@keyframes wave { +@keyframes pulsatingOpacity { 0% { - background-position: -46.8rem 0; + opacity: 1; + } + 25% { + opacity: 0.5; + } + 50% { + opacity: 0; + } + 75% { + opacity: 0.5; } 100% { - background-position: 46.8rem 0; + opacity: 1; } } - -@keyframes horizontal-shaking { - 0% { transform: translateX(0) } - 25% { transform: translateX(0.5rem) } - 50% { transform: translateX(-0.5rem) } - 75% { transform: translateX(0.5rem) } - 100% { transform: translateX(0) } -} - -@keyframes pulsatingOpacity { - 0% { opacity: 1 } - 25% { opacity: 0.5 } - 50% { opacity: 0 } - 75% { opacity: 0.5 } - 100% { opacity: 1 } - } - -@keyframes movingLeftAndRight { - 0% { left: -100% } - 25% { left: 0 } - 50% { left: 100% } - 75% { left: 75% } - 100% { left: -100% } -} diff --git a/client/src/styles/utils/_mixins.scss b/client/src/styles/utils/_mixins.scss index bb7033dad9..b9c2cbefc0 100644 --- a/client/src/styles/utils/_mixins.scss +++ b/client/src/styles/utils/_mixins.scss @@ -36,10 +36,6 @@ ); } -@mixin boldText() { - font-weight: $font-weight-superbold; -} - @mixin layoutOverflowBlurCommonProperties() { backdrop-filter: blur(2rem); -webkit-backdrop-filter: blur(2rem); @@ -81,16 +77,3 @@ */ font-variant-ligatures: no-contextual; } - -@mixin layoutFloatingElementWidth() { - width: 100%; - min-width: 39rem; -} - -@mixin tipTileConnectWalletImage() { - height: 8.2rem; - - @media #{$desktop-up} { - height: 11.3rem; - } -} diff --git a/client/src/styles/utils/_variables.scss b/client/src/styles/utils/_variables.scss index a61a651e06..c938bc584a 100644 --- a/client/src/styles/utils/_variables.scss +++ b/client/src/styles/utils/_variables.scss @@ -28,7 +28,6 @@ $z-index-7: 7; // modals $z-index-8: 8; // connect wallet modal // distances -$headerHeight: 7.2rem; $layoutMarginHorizontal: 2.4rem; $progressStepperSlimStepPadding: 2.4rem; $modalVariantSmallPaddingMobile: 2.4rem; diff --git a/client/src/svg/history.ts b/client/src/svg/history.ts deleted file mode 100644 index a4ab6efb84..0000000000 --- a/client/src/svg/history.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { SvgImageConfig } from 'components/ui/Svg/types'; - -export const allocate: SvgImageConfig = { - markup: - '', - viewBox: '0 0 40 40', -}; - -export const donation: SvgImageConfig = { - markup: - '', - viewBox: '0 0 40 40', -}; diff --git a/client/src/svg/misc.ts b/client/src/svg/misc.ts index db7b9a3227..d38c29a854 100644 --- a/client/src/svg/misc.ts +++ b/client/src/svg/misc.ts @@ -36,18 +36,6 @@ export const heart: SvgImageConfig = { viewBox: '0 0 32 32', }; -export const plus: SvgImageConfig = { - markup: - '', - viewBox: '0 0 15 15', -}; - -export const minus: SvgImageConfig = { - markup: - '', - viewBox: '0 0 14 2', -}; - export const notificationIconWarning: SvgImageConfig = { markup: '', @@ -78,12 +66,6 @@ export const checkMark: SvgImageConfig = { viewBox: '0 0 32 32', }; -export const pencil: SvgImageConfig = { - markup: - '', - viewBox: '0 0 24 24', -}; - export const share: SvgImageConfig = { markup: '', diff --git a/client/src/svg/navigation.ts b/client/src/svg/navigation.ts index aa945853cd..efe5b6ec9e 100644 --- a/client/src/svg/navigation.ts +++ b/client/src/svg/navigation.ts @@ -27,12 +27,6 @@ export const settings: SvgImageConfig = { viewBox: '0 0 22 22', }; -export const earn: SvgImageConfig = { - markup: - '', - viewBox: '0 0 32 32', -}; - export const chevronLeft: SvgImageConfig = { markup: '', diff --git a/client/src/views/AllocationView/AllocationView.module.scss b/client/src/views/AllocationView/AllocationView.module.scss deleted file mode 100644 index 23b0330906..0000000000 --- a/client/src/views/AllocationView/AllocationView.module.scss +++ /dev/null @@ -1,28 +0,0 @@ -.body.isWaitingForAllMultisigSignatures { - opacity: 0.5; -} - -.box { - &:not(:last-child) { - margin: 0 auto 1.6rem; - } -} - -.initialSignatureMessage { - color: $color-octant-orange; - display: block; - text-align: right; - margin-top: 0.6rem; - margin-bottom: -1rem; - - @media #{$desktop-up} { - margin-bottom: -1.8rem; - } - - .text { - font-weight: $font-weight-bold; - color: $color-octant-orange; - line-height: 1.4rem; - font-size: $font-size-10; - } -} diff --git a/client/src/components/Earn/EarnBoxGlmLock/index.tsx b/client/src/views/AllocationView/index.tsx similarity index 53% rename from client/src/components/Earn/EarnBoxGlmLock/index.tsx rename to client/src/views/AllocationView/index.tsx index ced474dc20..fcb4c21044 100644 --- a/client/src/components/Earn/EarnBoxGlmLock/index.tsx +++ b/client/src/views/AllocationView/index.tsx @@ -1,2 +1,2 @@ // eslint-disable-next-line no-restricted-exports -export { default } from './EarnBoxGlmLock'; +export { default } from './AllocationView'; diff --git a/client/src/views/AllocationView/types.ts b/client/src/views/AllocationView/types.ts deleted file mode 100644 index dc9a9db4e7..0000000000 --- a/client/src/views/AllocationView/types.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { UserAllocationElement } from 'hooks/queries/useUserAllocations'; - -export type UserAllocationElementString = Omit & { - value: string; -}; - -export type PercentageProportions = { - [key: string]: number; -}; - -export type AllocationValue = { - address: string; - value: string; -}; - -export type AllocationValues = AllocationValue[]; - -export type AllocationWithPositiveValueBigInt = { - projectAddress: string; - value: bigint; -}; diff --git a/client/src/views/AllocationView/utils.test.ts b/client/src/views/AllocationView/utils.test.ts deleted file mode 100644 index 906ee5ca70..0000000000 --- a/client/src/views/AllocationView/utils.test.ts +++ /dev/null @@ -1,379 +0,0 @@ -/* eslint-disable @typescript-eslint/naming-convention */ - -import { formatUnitsBigInt } from 'utils/formatUnitsBigInt'; -import { parseUnitsBigInt } from 'utils/parseUnitsBigInt'; - -import { - getAllocationValuesInitialState, - getAllocationValuesAfterManualChange, - getAllocationValuesWithRewardsSplitted, -} from './utils'; - -describe('getAllocationValuesWithRewardsSplitted', () => { - it('properly distributes the difference when sum is lower than restToDistribute', () => { - expect( - getAllocationValuesWithRewardsSplitted({ - allocationValues: [ - { address: '0xA', value: '0.2' }, - { address: '0xB', value: '0.3' }, - { address: '0xC', value: '0.5' }, - ], - restToDistribute: parseUnitsBigInt('2'), - }), - ).toEqual([ - { address: '0xA', value: '0.2' }, - { address: '0xB', value: '0.3' }, - { address: '0xC', value: '1.5' }, - ]); - }); - - it('properly distributes the difference when sum is lower than restToDistribute and last element cant fill the difference', () => { - expect( - getAllocationValuesWithRewardsSplitted({ - allocationValues: [ - { address: '0xA', value: '0.2' }, - { address: '0xB', value: '0.3' }, - { address: '0xC', value: '0' }, - ], - restToDistribute: parseUnitsBigInt('2'), - }), - ).toEqual([ - { address: '0xA', value: '1.7' }, - { address: '0xB', value: '0.3' }, - { address: '0xC', value: '0' }, - ]); - }); - - it('properly distributes the difference when sum is bigger than restToDistribute and last element can fill the difference', () => { - expect( - getAllocationValuesWithRewardsSplitted({ - allocationValues: [ - { address: '0xA', value: '1.0' }, - { address: '0xB', value: '2.0' }, - { address: '0xC', value: '4.5' }, - ], - restToDistribute: parseUnitsBigInt('5'), - }), - ).toEqual([ - { address: '0xA', value: '1.0' }, - { address: '0xB', value: '2.0' }, - { address: '0xC', value: '2' }, - ]); - }); - - it('properly distributes the difference when sum is bigger than restToDistribute and last element cant fill the difference', () => { - expect( - getAllocationValuesWithRewardsSplitted({ - allocationValues: [ - { address: '0xA', value: '1.0' }, - { address: '0xB', value: '4.5' }, - { address: '0xC', value: '0.5' }, - ], - restToDistribute: parseUnitsBigInt('5'), - }), - ).toEqual([ - { address: '0xA', value: '1.0' }, - { address: '0xB', value: '3.5' }, - { address: '0xC', value: '0.5' }, - ]); - }); - - it('returns empty array when given empty array', () => { - expect( - getAllocationValuesWithRewardsSplitted({ - allocationValues: [], - restToDistribute: BigInt(500), - }), - ).toEqual([]); - }); - - it('returns initial values when restToDistribute is zero', () => { - expect( - getAllocationValuesWithRewardsSplitted({ - allocationValues: [ - { address: '0xA', value: formatUnitsBigInt(BigInt(100)) }, - { address: '0xB', value: formatUnitsBigInt(BigInt(200)) }, - { address: '0xC', value: formatUnitsBigInt(BigInt(450)) }, - ], - restToDistribute: BigInt(0), - }), - ).toEqual([ - { address: '0xA', value: formatUnitsBigInt(BigInt(100)) }, - { address: '0xB', value: formatUnitsBigInt(BigInt(200)) }, - { address: '0xC', value: formatUnitsBigInt(BigInt(450)) }, - ]); - }); -}); - -describe('getAllocationValuesInitialState', () => { - const propsCommon = { - allocationValues: [ - { address: '0xA', value: '0.2' }, - { address: '0xB', value: '0.3' }, - { address: '0xC', value: '0.5' }, - ], - allocations: ['0xA', '0xB', '0xC'], - isManualMode: false, - percentageProportions: {}, - rewardsForProjects: parseUnitsBigInt('1'), - shouldReset: false, - userAllocationsElements: [], - }; - - describe('Case A (shouldReset, userAllocations provided)', () => { - it('when allocations match userAllocationsElements', () => { - expect( - getAllocationValuesInitialState({ - ...propsCommon, - allocations: ['0xA', '0xB', '0xC'], - isManualMode: true, - rewardsForProjects: parseUnitsBigInt('0.6'), - shouldReset: true, - userAllocationsElements: [ - { address: '0xA', value: '0.3' }, - { address: '0xB', value: '0.2' }, - { address: '0xC', value: '0.1' }, - ], - }), - ).toEqual([ - { address: '0xA', value: '0.3' }, - { address: '0xB', value: '0.2' }, - { address: '0xC', value: '0.1' }, - ]); - }); - - it('when allocations do not match userAllocationsElements', () => { - expect( - getAllocationValuesInitialState({ - ...propsCommon, - allocations: ['0xA', '0xB', '0xC', '0xD'], - isManualMode: true, - rewardsForProjects: parseUnitsBigInt('0.6'), - shouldReset: true, - userAllocationsElements: [ - { address: '0xA', value: '0.3' }, - { address: '0xB', value: '0.2' }, - { address: '0xC', value: '0.1' }, - ], - }), - ).toEqual([ - { address: '0xA', value: '0.3' }, - { address: '0xB', value: '0.2' }, - { address: '0xC', value: '0.1' }, - { address: '0xD', value: '0' }, - ]); - }); - }); - - describe('Case B (shouldReset, userAllocations not provided)', () => { - it('default', () => { - expect( - getAllocationValuesInitialState({ - ...propsCommon, - allocationValues: [ - { address: '0xA', value: '0.3' }, - { address: '0xB', value: '0.2' }, - { address: '0xC', value: '0.1' }, - ], - isManualMode: false, - rewardsForProjects: parseUnitsBigInt('1'), - userAllocationsElements: [], - }), - ).toEqual([ - { address: '0xA', value: '0.333333333333333333' }, - { address: '0xB', value: '0.333333333333333333' }, - { address: '0xC', value: '0.333333333333333334' }, - ]); - }); - }); - - describe('Case C (!isManualMode) ', () => { - it('when !isManualMode', () => { - expect( - getAllocationValuesInitialState({ - ...propsCommon, - isManualMode: false, - userAllocationsElements: [], - }), - ).toEqual([ - { address: '0xA', value: '0.333333333333333333' }, - { address: '0xB', value: '0.333333333333333333' }, - { address: '0xC', value: '0.333333333333333334' }, - ]); - }); - }); - - describe('Case D (all the rest)', () => { - it('when isManualMode, userAllocationsElements & allocationValues', () => { - expect( - getAllocationValuesInitialState({ - ...propsCommon, - allocationValues: [ - { address: '0xA', value: '0.2' }, - { address: '0xB', value: '0.3' }, - { address: '0xC', value: '0.5' }, - ], - isManualMode: true, - userAllocationsElements: [ - { address: '0xA', value: '0.5' }, - { address: '0xB', value: '0.3' }, - { address: '0xC', value: '0.2' }, - ], - }), - ).toEqual([ - { address: '0xA', value: '0.2' }, - { address: '0xB', value: '0.3' }, - { address: '0xC', value: '0.5' }, - ]); - }); - - it('when isManualMode, userAllocationsElements & !allocationValues', () => { - expect( - getAllocationValuesInitialState({ - ...propsCommon, - allocationValues: [], - isManualMode: true, - userAllocationsElements: [ - { address: '0xA', value: '0.5' }, - { address: '0xB', value: '0.3' }, - { address: '0xC', value: '0.2' }, - ], - }), - ).toEqual([ - { address: '0xA', value: '0.5' }, - { address: '0xB', value: '0.3' }, - { address: '0xC', value: '0.2' }, - ]); - }); - - it('when isManualMode, userAllocationsElements, allocationValues & percentageProportions', () => { - expect( - getAllocationValuesInitialState({ - ...propsCommon, - allocationValues: [ - { address: '0xA', value: '0.2' }, - { address: '0xB', value: '0.3' }, - { address: '0xC', value: '0.5' }, - ], - isManualMode: true, - percentageProportions: { - '0xA': 60, - '0xB': 35, - '0xC': 5, - }, - userAllocationsElements: [ - { address: '0xA', value: '0.5' }, - { address: '0xB', value: '0.3' }, - { address: '0xC', value: '0.2' }, - ], - }), - ).toEqual([ - { address: '0xA', value: '0.6' }, - { address: '0xB', value: '0.35' }, - { address: '0xC', value: '0.05' }, - ]); - }); - }); -}); - -describe('getAllocationValuesAfterManualChange', () => { - const propsCommon = { - allocationValues: [ - { address: '0xA', value: '0.333333333333333333' }, - { address: '0xB', value: '0.333333333333333333' }, - { address: '0xC', value: '0.333333333333333334' }, - ], - allocations: ['0xA', '0xB', '0xC'], - individualReward: parseUnitsBigInt('2'), - isManualMode: false, - newAllocationValue: { - address: '0xA', - value: '0.05', - }, - rewardsForProjects: parseUnitsBigInt('1'), - setAddressesWithError: () => {}, - }; - - it('!individualReward', () => { - expect( - getAllocationValuesAfterManualChange({ ...propsCommon, individualReward: undefined }), - ).toEqual({ - allocationValuesArrayNew: [ - { address: '0xA', value: '0.333333333333333333' }, - { address: '0xB', value: '0.333333333333333333' }, - { address: '0xC', value: '0.333333333333333334' }, - ], - rewardsForProjectsNew: parseUnitsBigInt('1'), - }); - }); - - it('allocationValuesArrayNewSum>(individualReward)', () => { - expect( - getAllocationValuesAfterManualChange({ - ...propsCommon, - newAllocationValue: { - address: '0xA', - value: '100', - }, - }), - ).toEqual({ - allocationValuesArrayNew: [ - { address: '0xA', value: '0' }, - { address: '0xB', value: '0.333333333333333333' }, - { address: '0xC', value: '0.333333333333333334' }, - ], - rewardsForProjectsNew: parseUnitsBigInt('1'), - }); - }); - - it('correctly updates allocationValues when isManualMode', () => { - expect( - getAllocationValuesAfterManualChange({ - ...propsCommon, - isManualMode: true, - }), - ).toEqual({ - allocationValuesArrayNew: [ - { address: '0xA', value: '0.05' }, - { address: '0xB', value: '0.333333333333333333' }, - { address: '0xC', value: '0.333333333333333334' }, - ], - rewardsForProjectsNew: parseUnitsBigInt('0.716666666666666667'), - }); - }); - - it('correctly updates allocationValues when !isManualMode', () => { - expect(getAllocationValuesAfterManualChange(propsCommon)).toEqual({ - allocationValuesArrayNew: [ - { address: '0xA', value: '0.05' }, - { address: '0xB', value: '0.333333333333333333' }, - { address: '0xC', value: '0.616666666666666667' }, - ], - rewardsForProjectsNew: parseUnitsBigInt('1'), - }); - }); - - it('correctly updates allocationValues when !isManualMode, rewardsForProjectsNew is zero when all values are 0', () => { - expect( - getAllocationValuesAfterManualChange({ - ...propsCommon, - allocationValues: [ - { address: '0xA', value: '0.333333333333333333' }, - { address: '0xB', value: '0' }, - { address: '0xC', value: '0' }, - ], - newAllocationValue: { - address: '0xA', - value: '0', - }, - }), - ).toEqual({ - allocationValuesArrayNew: [ - { address: '0xA', value: '0' }, - { address: '0xB', value: '0' }, - { address: '0xC', value: '0' }, - ], - rewardsForProjectsNew: parseUnitsBigInt('0'), - }); - }); -}); diff --git a/client/src/views/AllocationView/utils.ts b/client/src/views/AllocationView/utils.ts deleted file mode 100644 index aaab36aabd..0000000000 --- a/client/src/views/AllocationView/utils.ts +++ /dev/null @@ -1,292 +0,0 @@ -import React from 'react'; - -import { AllocationItemWithAllocations } from 'components/Allocation/AllocationItem/types'; -import { ProjectIpfsWithRewards } from 'hooks/queries/useProjectsIpfsWithRewards'; -import { formatUnitsBigInt } from 'utils/formatUnitsBigInt'; -import getSortedElementsByTotalValueOfAllocationsAndAlphabetical from 'utils/getSortedElementsByTotalValueOfAllocationsAndAlphabetical'; -import { parseUnitsBigInt } from 'utils/parseUnitsBigInt'; - -import { - AllocationValue, - AllocationValues, - PercentageProportions, - UserAllocationElementString, -} from './types'; - -export function getAllocationValuesWithRewardsSplitted({ - allocationValues, - restToDistribute, -}: { - allocationValues: AllocationValues; - restToDistribute: bigint; -}): AllocationValues { - if (allocationValues.length === 0) { - return []; - } - const allocationValuesNew = [...allocationValues]; - const allocationValuesSum = allocationValuesNew.reduce((acc, { value }) => { - return acc + parseUnitsBigInt(value); - }, BigInt(0)); - - if (restToDistribute === 0n) { - return allocationValues; - } - - /** - * Since percentage calculated in getAllocationValuesInitialState is not perfect, - * chances are allocationValuesSum is lower than restToDistribute. - */ - if (allocationValuesSum < restToDistribute) { - const difference = restToDistribute - allocationValuesSum; - const lastElementValue = parseUnitsBigInt( - allocationValuesNew[allocationValuesNew.length - 1].value, - ); - - // We don't want to add value to element user chose to set as 0. - if (lastElementValue > 0n) { - allocationValuesNew[allocationValuesNew.length - 1].value = formatUnitsBigInt( - lastElementValue + difference, - ); - } else { - // Find first non-zero element. - const elementIndexToChange = allocationValuesNew.findIndex( - element => parseUnitsBigInt(element.value) > 0n, - ); - allocationValuesNew[elementIndexToChange].value = formatUnitsBigInt( - parseUnitsBigInt(allocationValuesNew[elementIndexToChange].value) + difference, - ); - } - } - /** - * Since percentage calculated in getAllocationValuesInitialState is not perfect, - * chances are allocationValuesSum is bigger than restToDistribute. - */ - if (allocationValuesSum > restToDistribute) { - const difference = allocationValuesSum - restToDistribute; - const lastElementValue = parseUnitsBigInt( - allocationValuesNew[allocationValuesNew.length - 1].value, - ); - - if (lastElementValue >= difference) { - allocationValuesNew[allocationValuesNew.length - 1].value = formatUnitsBigInt( - lastElementValue - difference, - ); - } else { - const elementIndexToChange = allocationValuesNew.findIndex( - element => parseUnitsBigInt(element.value) > difference, - ); - allocationValuesNew[elementIndexToChange].value = formatUnitsBigInt( - parseUnitsBigInt(allocationValuesNew[elementIndexToChange].value) - difference, - ); - } - } - - return allocationValuesNew; -} - -export function getAllocationValuesInitialState({ - allocationValues, - allocations, - isManualMode, - percentageProportions, - rewardsForProjects, - shouldReset, - userAllocationsElements, -}: { - allocationValues: AllocationValues; - allocations: string[]; - isManualMode: boolean; - percentageProportions: PercentageProportions; - rewardsForProjects: bigint; - shouldReset: boolean; - userAllocationsElements: UserAllocationElementString[]; -}): AllocationValues { - if (shouldReset) { - const allocationValuesNew = allocations.map(allocation => { - // Case A (see utils.test.ts). - if (userAllocationsElements.length > 0) { - const userAllocationValue = userAllocationsElements.find( - element => element.address === allocation, - )?.value; - const userAllocationValueFinal = userAllocationValue || '0'; - return { - address: allocation, - // @ts-expect-error TS method collision. - value: userAllocationValueFinal.toLocaleString('fullWide', { useGrouping: false }), - }; - } - // Case B (see utils.test.ts). - return { - address: allocation, - // @ts-expect-error TS method collision. - value: '0'.toLocaleString('fullWide', { useGrouping: false }), - }; - }); - - return getAllocationValuesWithRewardsSplitted({ - allocationValues: allocationValuesNew, - restToDistribute: BigInt(0), - }); - } - if (!isManualMode) { - // Case C (see utils.test.ts). - const allocationValuesNew = allocations.map(allocation => ({ - address: allocation, - value: formatUnitsBigInt(rewardsForProjects / BigInt(allocations.length)).toLocaleString( - // @ts-expect-error TS method collision. - 'fullWide', - { - useGrouping: false, - }, - ), - })); - - return getAllocationValuesWithRewardsSplitted({ - allocationValues: allocationValuesNew, - restToDistribute: rewardsForProjects, - }); - } - // Case D (see utils.test.ts). - - const allocationValuesNew = allocations.map(allocation => { - const percentageProportion = percentageProportions[allocation]; - const allocationValue = allocationValues.find(element => element.address === allocation)?.value; - const userAllocationValue = userAllocationsElements.find( - element => element.address === allocation, - )?.value; - const userValue = allocationValue || userAllocationValue || '0'; - // Value for the project set as valueUser multiplied by percentage. - const value = ( - percentageProportion === undefined - ? userValue - : formatUnitsBigInt((rewardsForProjects * BigInt(percentageProportion)) / 100n) - ) - // @ts-expect-error TS method collision. - .toLocaleString('fullWide', { useGrouping: false }); - return { - address: allocation, - value, - }; - }); - - return getAllocationValuesWithRewardsSplitted({ - allocationValues: allocationValuesNew, - restToDistribute: rewardsForProjects, - }); -} - -export function getAllocationsWithRewards({ - projectsIpfsWithRewards, - allocationValues, - areAllocationsAvailableOrAlreadyDone, - userAllocationsElements, -}: { - allocationValues: AllocationValues | undefined; - areAllocationsAvailableOrAlreadyDone: boolean; - projectsIpfsWithRewards: ProjectIpfsWithRewards[]; - userAllocationsElements: UserAllocationElementString[] | undefined; -}): AllocationItemWithAllocations[] { - const isDataDefined = - projectsIpfsWithRewards && - projectsIpfsWithRewards.length > 0 && - areAllocationsAvailableOrAlreadyDone; - let allocationsWithRewards = isDataDefined - ? allocationValues!.map(allocationValue => { - const projectIpfsWithRewards = projectsIpfsWithRewards.find( - ({ address }) => address === allocationValue.address, - )!; - const isAllocatedTo = !!userAllocationsElements?.find( - ({ address }) => address === allocationValue.address, - ); - - return { - isAllocatedTo, - ...allocationValue, - ...projectIpfsWithRewards, - }; - }) - : []; - - allocationsWithRewards.sort(({ value: valueA }, { value: valueB }) => { - const valueABigInt = parseUnitsBigInt(valueA || '0'); - const valueBBigInt = parseUnitsBigInt(valueB || '0'); - if (valueABigInt < valueBBigInt) { - return 1; - } - if (valueABigInt > valueBBigInt) { - return -1; - } - return 0; - }); - - allocationsWithRewards = getSortedElementsByTotalValueOfAllocationsAndAlphabetical( - allocationsWithRewards as AllocationItemWithAllocations[], - ) as AllocationItemWithAllocations[]; - - return allocationsWithRewards; -} - -export function getAllocationValuesAfterManualChange({ - newAllocationValue, - allocationValues, - rewardsForProjects, - individualReward, - isManualMode, - setAddressesWithError, -}: { - allocationValues: AllocationValues; - individualReward: bigint | undefined; - isManualMode: boolean; - newAllocationValue: AllocationValue; - rewardsForProjects: bigint; - setAddressesWithError: React.Dispatch>; -}): { allocationValuesArrayNew: AllocationValues; rewardsForProjectsNew: bigint } { - if (!individualReward) { - return { - allocationValuesArrayNew: allocationValues, - rewardsForProjectsNew: rewardsForProjects, - }; - } - - const allocationValuesArrayNew = allocationValues.map(element => ({ - ...element, - value: - element.address === newAllocationValue.address ? newAllocationValue.value : element.value, - })); - - const allocationValuesArrayNewSum = allocationValuesArrayNew.reduce( - (acc, { value }) => acc + parseUnitsBigInt(value || '0'), - BigInt(0), - ); - - if (allocationValuesArrayNewSum > individualReward) { - setAddressesWithError(prev => [...prev, newAllocationValue.address]); - return { - allocationValuesArrayNew: allocationValues.map(element => ({ - ...element, - value: element.address === newAllocationValue.address ? '0' : element.value, - })), - rewardsForProjectsNew: rewardsForProjects, - }; - } - - if (isManualMode) { - return { - allocationValuesArrayNew, - rewardsForProjectsNew: allocationValuesArrayNewSum, - }; - } - - const rewardsForProjectsNew = allocationValuesArrayNewSum === 0n ? BigInt(0) : rewardsForProjects; - - return { - allocationValuesArrayNew: - allocationValuesArrayNew.length === 1 - ? allocationValuesArrayNew - : getAllocationValuesWithRewardsSplitted({ - allocationValues: allocationValuesArrayNew, - restToDistribute: rewardsForProjectsNew, - }), - rewardsForProjectsNew, - }; -} diff --git a/client/src/views/EarnView/EarnView.module.scss b/client/src/views/EarnView/EarnView.module.scss deleted file mode 100644 index 7121dd9a61..0000000000 --- a/client/src/views/EarnView/EarnView.module.scss +++ /dev/null @@ -1,38 +0,0 @@ -.box { - &:not(:first-child) { - margin: 1.6rem auto 0; - } -} - -.column { - &:not(:last-child) { - margin-bottom: 1.6rem; - } - - @media #{$desktop-up} { - @include flexBasisGutter(2, 1.6rem); - margin-bottom: 0; - } -} - -.boxesWrapper { - width: 100%; -} - -.wrapper { - display: flex; - width: 100%; - height: 100%; - flex-direction: column; - align-items: start; - min-height: 0; - - @media #{$desktop-up} { - flex-direction: row; - justify-content: space-between; - } -} - -.preLaunchTimer { - margin-top: 0.4rem; -} diff --git a/client/src/views/EarnView/EarnView.tsx b/client/src/views/EarnView/EarnView.tsx deleted file mode 100644 index 82fbf677d5..0000000000 --- a/client/src/views/EarnView/EarnView.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import cx from 'classnames'; -import React, { ReactElement, useState, useEffect } from 'react'; -import { useTranslation } from 'react-i18next'; - -import EarnBoxGlmLock from 'components/Earn/EarnBoxGlmLock'; -import EarnBoxPersonalAllocation from 'components/Earn/EarnBoxPersonalAllocation'; -import EarnHistory from 'components/Earn/EarnHistory'; -import EarnTipTiles from 'components/Earn/EarnTipTiles'; -import Layout from 'components/shared/Layout'; -import TimeCounter from 'components/shared/TimeCounter'; -import BoxRounded from 'components/ui/BoxRounded'; -import useIsProjectAdminMode from 'hooks/helpers/useIsProjectAdminMode'; -import useCurrentEpoch from 'hooks/queries/useCurrentEpoch'; -import getIsPreLaunch from 'utils/getIsPreLaunch'; - -import styles from './EarnView.module.scss'; - -const EarnView = (): ReactElement => { - const { t } = useTranslation('translation', { - keyPrefix: 'views.earn', - }); - const [isPollingForCurrentEpoch, setIsPollingForCurrentEpoch] = useState(false); - const { data: currentEpoch } = useCurrentEpoch({ - refetchInterval: isPollingForCurrentEpoch ? 5000 : false, - }); - - const isProjectAdminMode = useIsProjectAdminMode(); - - useEffect(() => { - // When Epoch 0 ends, we poll for Epoch 1 from the backend. - if (isPollingForCurrentEpoch && currentEpoch === 1) { - setIsPollingForCurrentEpoch(false); - } - }, [isPollingForCurrentEpoch, currentEpoch, setIsPollingForCurrentEpoch]); - - const isPreLaunch = getIsPreLaunch(currentEpoch); - - const preLaunchStartTimestamp = Date.UTC(2023, 7, 4, 10, 0, 0, 0); // 04.08.2023 12:00 CEST - const preLaunchEndTimestamp = Date.UTC(2023, 7, 8, 16, 0, 0, 0); // 08.08.2023 18:00 CEST - const duration = preLaunchEndTimestamp - preLaunchStartTimestamp; - - return ( - - -
-
- {isPreLaunch && ( - - setIsPollingForCurrentEpoch(true)} - timestamp={preLaunchEndTimestamp} - /> - - )} - {!isProjectAdminMode && } - -
- -
-
- ); -}; - -export default EarnView; diff --git a/client/src/components/Earn/EarnGlmLock/index.tsx b/client/src/views/HomeView/index.tsx similarity index 54% rename from client/src/components/Earn/EarnGlmLock/index.tsx rename to client/src/views/HomeView/index.tsx index 51932492ba..9283ddcc87 100644 --- a/client/src/components/Earn/EarnGlmLock/index.tsx +++ b/client/src/views/HomeView/index.tsx @@ -1,2 +1,2 @@ // eslint-disable-next-line no-restricted-exports -export { default } from './EarnGlmLock'; +export { default } from './HomeView'; diff --git a/client/src/views/MetricsView/MetricsView.module.scss b/client/src/views/MetricsView/MetricsView.module.scss deleted file mode 100644 index c2023686c9..0000000000 --- a/client/src/views/MetricsView/MetricsView.module.scss +++ /dev/null @@ -1,22 +0,0 @@ -.header { - width: 100%; - display: flex; - align-items: center; - color: $color-octant-dark; - font-size: $font-size-24; - font-weight: $font-weight-bold; - height: 8.8rem; - - @media #{$tablet-up} { - height: 14.4rem; - font-size: $font-size-32; - } - - @media #{$desktop-up} { - font-size: $font-size-40; - } - - @media #{$large-desktop-up} { - font-size: $font-size-48; - } -} diff --git a/client/src/views/MetricsView/MetricsView.tsx b/client/src/views/MetricsView/MetricsView.tsx index 12606269f3..3aa8e2d4ae 100644 --- a/client/src/views/MetricsView/MetricsView.tsx +++ b/client/src/views/MetricsView/MetricsView.tsx @@ -3,11 +3,10 @@ import { useTranslation } from 'react-i18next'; import MetricsEpoch from 'components/Metrics/MetricsEpoch'; import MetricsGeneral from 'components/Metrics/MetricsGeneral/MetricsGeneral'; +import ViewTitle from 'components/shared/ViewTitle/ViewTitle'; import { MetricsEpochProvider } from 'hooks/helpers/useMetrcisEpoch'; import useCurrentEpoch from 'hooks/queries/useCurrentEpoch'; -import styles from './MetricsView.module.scss'; - const MetricsView = (): ReactElement => { const { t } = useTranslation('translation', { keyPrefix: 'views.metrics' }); const { data: currentEpoch } = useCurrentEpoch(); @@ -21,10 +20,10 @@ const MetricsView = (): ReactElement => { {/* Workaround for epoch 0 allocation window (no epoch 0 metrics) */} {/* useMetricsEpoch.tsx:19 -> const lastEpoch = currentEpoch! - 1; */} {currentEpoch === 1 ? ( - "It's Epoch 1, so there are no metrics for the past. It's just a placeholder, please come back in Epoch 2." + t('epoch1Info') ) : ( <> -
{t('exploreTheData')}
+ {t('exploreTheData')} diff --git a/client/src/components/Earn/EarnHistory/index.tsx b/client/src/views/MetricsView/index.tsx similarity index 54% rename from client/src/components/Earn/EarnHistory/index.tsx rename to client/src/views/MetricsView/index.tsx index fffff26b2a..a3b62fd22a 100644 --- a/client/src/components/Earn/EarnHistory/index.tsx +++ b/client/src/views/MetricsView/index.tsx @@ -1,2 +1,2 @@ // eslint-disable-next-line no-restricted-exports -export { default } from './EarnHistory'; +export { default } from './MetricsView'; diff --git a/client/src/views/PlaygroundView/index.tsx b/client/src/views/PlaygroundView/index.tsx new file mode 100644 index 0000000000..d44bbd8796 --- /dev/null +++ b/client/src/views/PlaygroundView/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './PlaygroundView'; diff --git a/client/src/components/Earn/EarnTipTiles/index.tsx b/client/src/views/ProjectView/index.tsx similarity index 54% rename from client/src/components/Earn/EarnTipTiles/index.tsx rename to client/src/views/ProjectView/index.tsx index 6211f7158a..e6e39a9f52 100644 --- a/client/src/components/Earn/EarnTipTiles/index.tsx +++ b/client/src/views/ProjectView/index.tsx @@ -1,2 +1,2 @@ // eslint-disable-next-line no-restricted-exports -export { default } from './EarnTipTiles'; +export { default } from './ProjectView'; diff --git a/client/src/views/ProjectsView/index.tsx b/client/src/views/ProjectsView/index.tsx new file mode 100644 index 0000000000..383261523a --- /dev/null +++ b/client/src/views/ProjectsView/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './ProjectsView'; diff --git a/client/src/views/SettingsView/index.tsx b/client/src/views/SettingsView/index.tsx new file mode 100644 index 0000000000..41e634faf3 --- /dev/null +++ b/client/src/views/SettingsView/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './SettingsView'; diff --git a/client/src/views/SyncView/index.tsx b/client/src/views/SyncView/index.tsx new file mode 100644 index 0000000000..8b285f46d0 --- /dev/null +++ b/client/src/views/SyncView/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './SyncView'; From 67ee7b86fce1fafe85309aeda5a2f60bc9cf53df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Garbaci=C5=84ski?= <57113816+kgarbacinski@users.noreply.github.com> Date: Tue, 24 Sep 2024 18:58:34 +0300 Subject: [PATCH 124/241] OCT-1943: Add project searching on the server side (#419) ## Description ## Definition of Done 1. [ ] If required, the desciption of your change is added to the [QA changelog](https://www.notion.so/octantapp/Changelog-for-the-QA-d96fa3b411cf488bb1d8d9a598d88281) 2. [ ] Acceptance criteria are met. 3. [ ] PR is manually tested before the merge by developer(s). - [ ] Happy path is manually checked. 4. [ ] PR is manually tested by QA when their assistance is required (1). - [ ] Octant Areas & Test Cases are checked for impact and updated if required (2). 5. [ ] Unit tests are added unless there is a reason to omit them. 6. [ ] Automated tests are added when required. 7. [ ] The code is merged. 8. [ ] Tech documentation is added / updated, reviewed and approved (including mandatory approval by a code owner, should such exist for changed files). - [ ] BE: Swagger documentation is updated. 9. [ ] When required by QA: - [ ] Deployed to the relevant environment. - [ ] Passed system tests. --- (1) Developer(s) in coordination with QA decide whether it's required. For small tickets introducing small changes QA assistance is most probably not required. (2) [Octant Areas & Test Cases](https://docs.google.com/spreadsheets/d/1cRe6dxuKJV3a4ZskAwWEPvrFkQm6rEfyUCYwLTYw_Cc). --- backend/app/infrastructure/database/models.py | 16 +++ .../database/projects_details.py | 7 + backend/app/infrastructure/routes/projects.py | 59 +++++++- .../app/modules/modules_factory/current.py | 6 + .../app/modules/modules_factory/finalized.py | 6 + .../app/modules/modules_factory/finalizing.py | 6 + backend/app/modules/modules_factory/future.py | 11 +- .../app/modules/modules_factory/pending.py | 6 + .../modules/modules_factory/pre_pending.py | 6 + .../app/modules/modules_factory/protocols.py | 9 ++ .../modules/projects/details/controller.py | 19 +++ backend/app/modules/projects/details/core.py | 20 +++ .../details/service/projects_details.py | 35 +++++ backend/migrations/__init__.py | 0 backend/migrations/ipfs_integration/config.py | 17 +++ backend/migrations/ipfs_integration/core.py | 19 +++ .../migrations/ipfs_integration/files/cids.md | 5 + .../files/ipfs_projects_details_epoch_1.json | 100 ++++++++++++++ .../files/ipfs_projects_details_epoch_2.json | 100 ++++++++++++++ .../files/ipfs_projects_details_epoch_3.json | 124 +++++++++++++++++ .../files/ipfs_projects_details_epoch_4.json | 124 +++++++++++++++++ backend/migrations/ipfs_integration/logic.py | 127 ++++++++++++++++++ .../ipfs_integration/migration_helpers.py | 53 ++++++++ .../87b2cefcfa11_add_projectsdetails_table.py | 32 +++++ ...fa8_add_projects_details_from_ipfs_for_.py | 28 ++++ backend/tests/modules/projects/conftest.py | 11 ++ .../projects/details/test_filtering_core.py | 71 ++++++++++ .../details/test_projects_filtering.py | 34 +++++ backend/tests/modules/projects/helpers.py | 12 ++ .../metadata/test_projects_retrieval.py | 1 - 30 files changed, 1061 insertions(+), 3 deletions(-) create mode 100644 backend/app/infrastructure/database/projects_details.py create mode 100644 backend/app/modules/projects/details/controller.py create mode 100644 backend/app/modules/projects/details/core.py create mode 100644 backend/app/modules/projects/details/service/projects_details.py create mode 100644 backend/migrations/__init__.py create mode 100644 backend/migrations/ipfs_integration/config.py create mode 100644 backend/migrations/ipfs_integration/core.py create mode 100644 backend/migrations/ipfs_integration/files/cids.md create mode 100644 backend/migrations/ipfs_integration/files/ipfs_projects_details_epoch_1.json create mode 100644 backend/migrations/ipfs_integration/files/ipfs_projects_details_epoch_2.json create mode 100644 backend/migrations/ipfs_integration/files/ipfs_projects_details_epoch_3.json create mode 100644 backend/migrations/ipfs_integration/files/ipfs_projects_details_epoch_4.json create mode 100644 backend/migrations/ipfs_integration/logic.py create mode 100644 backend/migrations/ipfs_integration/migration_helpers.py create mode 100644 backend/migrations/versions/87b2cefcfa11_add_projectsdetails_table.py create mode 100644 backend/migrations/versions/c34003767fa8_add_projects_details_from_ipfs_for_.py create mode 100644 backend/tests/modules/projects/conftest.py create mode 100644 backend/tests/modules/projects/details/test_filtering_core.py create mode 100644 backend/tests/modules/projects/details/test_projects_filtering.py create mode 100644 backend/tests/modules/projects/helpers.py diff --git a/backend/app/infrastructure/database/models.py b/backend/app/infrastructure/database/models.py index cada6115b5..4b3f4aae33 100644 --- a/backend/app/infrastructure/database/models.py +++ b/backend/app/infrastructure/database/models.py @@ -204,3 +204,19 @@ class UniquenessQuotient(BaseModel): @property def validated_score(self): return Decimal(self.score) + + +class ProjectsDetails(BaseModel): + """ + This model represents the details of a project that is consistent with data on the IPFS node. + Records here are created only by migrations. + """ + + __tablename__ = "project_details" + + id = Column(db.Integer, primary_key=True) + address = Column(db.String(42), nullable=False) + name = Column(db.String, nullable=False) + epoch = Column(db.Integer, nullable=False) + + __table_args__ = (UniqueConstraint("address", "epoch", name="uq_address_epoch"),) diff --git a/backend/app/infrastructure/database/projects_details.py b/backend/app/infrastructure/database/projects_details.py new file mode 100644 index 0000000000..018ba587b1 --- /dev/null +++ b/backend/app/infrastructure/database/projects_details.py @@ -0,0 +1,7 @@ +from typing import List + +from app.infrastructure.database.models import ProjectsDetails + + +def get_projects_details_for_epoch(epoch: int) -> List[ProjectsDetails]: + return ProjectsDetails.query.filter_by(epoch=epoch).all() diff --git a/backend/app/infrastructure/routes/projects.py b/backend/app/infrastructure/routes/projects.py index bd2489a943..ecd60cf6db 100644 --- a/backend/app/infrastructure/routes/projects.py +++ b/backend/app/infrastructure/routes/projects.py @@ -1,4 +1,4 @@ -from flask import current_app as app +from flask import current_app as app, request from flask_restx import Namespace, fields from app.extensions import api @@ -7,6 +7,7 @@ from app.modules.projects.metadata.controller import ( get_projects_metadata, ) +from app.modules.projects.details import controller as projects_details_controller ns = Namespace("projects", description="Octant projects") api.add_namespace(ns) @@ -23,6 +24,29 @@ }, ) +projects_details_model = api.model( + "ProjectsDetails", + { + "projects_details": fields.List( + fields.Nested( + api.model( + "ProjectsDetails", + { + "name": fields.String( + required=True, description="Project name" + ), + "address": fields.String( + required=True, description="Project address" + ), + }, + ) + ), + required=False, + description="Projects details", + ), + }, +) + @ns.route("/epoch/") @ns.doc( @@ -43,3 +67,36 @@ def get(self, epoch): "projectsAddresses": projects_metadata.projects_addresses, "projectsCid": projects_metadata.projects_cid, } + + +@ns.route("/details/epoch/") +@ns.doc( + description="Returns projects details for a given epoch and searchPhrase.", + params={ + "epoch": "Epoch number", + "searchPhrase": "Search phrase (query parameter)", + }, +) +class ProjectsDetails(OctantResource): + @ns.marshal_with(projects_details_model) + @ns.response(200, "Projects metadata is successfully retrieved") + def get(self, epoch: int): + search_phrase = request.args.get("searchPhrase", "") + + app.logger.debug( + f"Getting projects details for epoch {epoch} and search phrase {search_phrase}" + ) + projects_details = projects_details_controller.get_projects_details( + epoch, search_phrase + ) + app.logger.debug(f"Projects details for epoch: {epoch}: {projects_details}") + + return { + "projects_details": [ + { + "name": project["name"], + "address": project["address"], + } + for project in projects_details + ] + } diff --git a/backend/app/modules/modules_factory/current.py b/backend/app/modules/modules_factory/current.py index a95a8fa744..32d0177e9a 100644 --- a/backend/app/modules/modules_factory/current.py +++ b/backend/app/modules/modules_factory/current.py @@ -15,6 +15,7 @@ UserAllocationNonceProtocol, ScoreDelegation, UniquenessQuotients, + ProjectsDetailsService, ) from app.modules.modules_factory.protocols import SimulatePendingSnapshots from app.modules.multisig_signatures.service.offchain import OffchainMultisigSignatures @@ -45,6 +46,9 @@ from app.pydantic import Model from app.shared.blockchain_types import compare_blockchain_types, ChainTypes from app.constants import UQ_THRESHOLD_MAINNET, UQ_THRESHOLD_NOT_MAINNET +from app.modules.projects.details.service.projects_details import ( + StaticProjectsDetailsService, +) class CurrentUserDeposits(UserEffectiveDeposits, TotalEffectiveDeposits, Protocol): @@ -61,6 +65,7 @@ class CurrentServices(Model): simulated_pending_snapshot_service: SimulatePendingSnapshots multisig_signatures_service: MultisigSignatures projects_metadata_service: ProjectsMetadataService + projects_details_service: ProjectsDetailsService user_budgets_service: UpcomingUserBudgets score_delegation_service: ScoreDelegation uniqueness_quotients: UniquenessQuotients @@ -144,6 +149,7 @@ def create(chain_id: int) -> "CurrentServices": user_tos_service=user_tos, user_antisybil_service=user_antisybil_service, projects_metadata_service=StaticProjectsMetadataService(), + projects_details_service=StaticProjectsDetailsService(), user_budgets_service=user_budgets, score_delegation_service=score_delegation, uniqueness_quotients=uniqueness_quotients, diff --git a/backend/app/modules/modules_factory/finalized.py b/backend/app/modules/modules_factory/finalized.py index d5b470c3d6..20cb433f2b 100644 --- a/backend/app/modules/modules_factory/finalized.py +++ b/backend/app/modules/modules_factory/finalized.py @@ -13,6 +13,7 @@ WithdrawalsService, SavedProjectRewardsService, ProjectsMetadataService, + ProjectsDetailsService, ) from app.modules.octant_rewards.general.service.finalized import FinalizedOctantRewards from app.modules.projects.rewards.service.saved import SavedProjectRewards @@ -26,6 +27,9 @@ StaticProjectsMetadataService, ) from app.pydantic import Model +from app.modules.projects.details.service.projects_details import ( + StaticProjectsDetailsService, +) class FinalizedOctantRewardsProtocol(OctantRewards, Leverage, Protocol): @@ -52,6 +56,7 @@ class FinalizedServices(Model): withdrawals_service: WithdrawalsService project_rewards_service: SavedProjectRewardsService projects_metadata_service: ProjectsMetadataService + projects_details_service: ProjectsDetailsService @staticmethod def create() -> "FinalizedServices": @@ -75,4 +80,5 @@ def create() -> "FinalizedServices": withdrawals_service=withdrawals_service, project_rewards_service=SavedProjectRewards(), projects_metadata_service=StaticProjectsMetadataService(), + projects_details_service=StaticProjectsDetailsService(), ) diff --git a/backend/app/modules/modules_factory/finalizing.py b/backend/app/modules/modules_factory/finalizing.py index 44e409221c..3c38584c7f 100644 --- a/backend/app/modules/modules_factory/finalizing.py +++ b/backend/app/modules/modules_factory/finalizing.py @@ -13,6 +13,7 @@ WithdrawalsService, SavedProjectRewardsService, ProjectsMetadataService, + ProjectsDetailsService, ) from app.modules.octant_rewards.general.service.pending import PendingOctantRewards from app.modules.octant_rewards.matched.pending import PendingOctantMatchedRewards @@ -29,6 +30,9 @@ StaticProjectsMetadataService, ) from app.pydantic import Model +from app.modules.projects.details.service.projects_details import ( + StaticProjectsDetailsService, +) class FinalizingOctantRewards(OctantRewards, Leverage, Protocol): @@ -50,6 +54,7 @@ class FinalizingServices(Model): withdrawals_service: WithdrawalsService project_rewards_service: SavedProjectRewardsService projects_metadata_service: ProjectsMetadataService + projects_details_service: ProjectsDetailsService @staticmethod def create() -> "FinalizingServices": @@ -90,4 +95,5 @@ def create() -> "FinalizingServices": withdrawals_service=withdrawals_service, project_rewards_service=SavedProjectRewards(), projects_metadata_service=StaticProjectsMetadataService(), + projects_details_service=StaticProjectsDetailsService(), ) diff --git a/backend/app/modules/modules_factory/future.py b/backend/app/modules/modules_factory/future.py index 51fd029936..64ea69165c 100644 --- a/backend/app/modules/modules_factory/future.py +++ b/backend/app/modules/modules_factory/future.py @@ -1,4 +1,8 @@ -from app.modules.modules_factory.protocols import OctantRewards, ProjectsMetadataService +from app.modules.modules_factory.protocols import ( + OctantRewards, + ProjectsMetadataService, + ProjectsDetailsService, +) from app.modules.octant_rewards.general.service.calculated import ( CalculatedOctantRewards, ) @@ -10,11 +14,15 @@ StaticProjectsMetadataService, ) from app.pydantic import Model +from app.modules.projects.details.service.projects_details import ( + StaticProjectsDetailsService, +) class FutureServices(Model): octant_rewards_service: OctantRewards projects_metadata_service: ProjectsMetadataService + projects_details_service: ProjectsDetailsService @staticmethod def create() -> "FutureServices": @@ -24,4 +32,5 @@ def create() -> "FutureServices": effective_deposits=ContractBalanceUserDeposits(), ), projects_metadata_service=StaticProjectsMetadataService(), + projects_details_service=StaticProjectsDetailsService(), ) diff --git a/backend/app/modules/modules_factory/pending.py b/backend/app/modules/modules_factory/pending.py index 5989d42d98..2eec837023 100644 --- a/backend/app/modules/modules_factory/pending.py +++ b/backend/app/modules/modules_factory/pending.py @@ -20,6 +20,7 @@ MultisigSignatures, ProjectsMetadataService, UniquenessQuotients, + ProjectsDetailsService, ) from app.modules.multisig_signatures.service.offchain import OffchainMultisigSignatures from app.modules.octant_rewards.general.service.pending import PendingOctantRewards @@ -49,6 +50,9 @@ from app.pydantic import Model from app.shared.blockchain_types import compare_blockchain_types, ChainTypes from app.constants import UQ_THRESHOLD_MAINNET, UQ_THRESHOLD_NOT_MAINNET +from app.modules.projects.details.service.projects_details import ( + StaticProjectsDetailsService, +) class PendingOctantRewardsService(OctantRewards, Leverage, Protocol): @@ -87,6 +91,7 @@ class PendingServices(Model): project_rewards_service: PendingProjectRewardsProtocol multisig_signatures_service: MultisigSignatures projects_metadata_service: ProjectsMetadataService + projects_details_service: ProjectsDetailsService uniqueness_quotients: UniquenessQuotients @staticmethod @@ -159,5 +164,6 @@ def create(chain_id: int) -> "PendingServices": project_rewards_service=project_rewards, multisig_signatures_service=multisig_signatures, projects_metadata_service=StaticProjectsMetadataService(), + projects_details_service=StaticProjectsDetailsService(), uniqueness_quotients=uniqueness_quotients, ) diff --git a/backend/app/modules/modules_factory/pre_pending.py b/backend/app/modules/modules_factory/pre_pending.py index 46867a8be2..e5bfd8c63b 100644 --- a/backend/app/modules/modules_factory/pre_pending.py +++ b/backend/app/modules/modules_factory/pre_pending.py @@ -9,6 +9,7 @@ UserEffectiveDeposits, SavedProjectRewardsService, ProjectsMetadataService, + ProjectsDetailsService, ) from app.modules.octant_rewards.general.service.calculated import ( CalculatedOctantRewards, @@ -24,6 +25,9 @@ ) from app.pydantic import Model from app.shared.blockchain_types import compare_blockchain_types, ChainTypes +from app.modules.projects.details.service.projects_details import ( + StaticProjectsDetailsService, +) class PrePendingUserDeposits(UserEffectiveDeposits, AllUserEffectiveDeposits, Protocol): @@ -36,6 +40,7 @@ class PrePendingServices(Model): pending_snapshots_service: PendingSnapshots project_rewards_service: SavedProjectRewardsService projects_metadata_service: ProjectsMetadataService + projects_details_service: ProjectsDetailsService @staticmethod def create(chain_id: int) -> "PrePendingServices": @@ -63,4 +68,5 @@ def create(chain_id: int) -> "PrePendingServices": pending_snapshots_service=pending_snapshots_service, project_rewards_service=SavedProjectRewards(), projects_metadata_service=StaticProjectsMetadataService(), + projects_details_service=StaticProjectsDetailsService(), ) diff --git a/backend/app/modules/modules_factory/protocols.py b/backend/app/modules/modules_factory/protocols.py index f761b02585..0bb8f4957d 100644 --- a/backend/app/modules/modules_factory/protocols.py +++ b/backend/app/modules/modules_factory/protocols.py @@ -19,6 +19,7 @@ ) from app.modules.history.dto import UserHistoryDTO from app.modules.multisig_signatures.dto import Signature +from app.modules.projects.details.service.projects_details import ProjectsDetailsDTO @runtime_checkable @@ -226,6 +227,14 @@ def get_projects_metadata(self, context: Context) -> ProjectsMetadata: ... +@runtime_checkable +class ProjectsDetailsService(Protocol): + def get_projects_details_by_search_phrase( + self, context: Context, search_phrase: str + ) -> ProjectsDetailsDTO: + ... + + @runtime_checkable class UserAllocationNonceProtocol(Protocol): def get_user_next_nonce(self, user_address: str) -> int: diff --git a/backend/app/modules/projects/details/controller.py b/backend/app/modules/projects/details/controller.py new file mode 100644 index 0000000000..2f0dc59a1f --- /dev/null +++ b/backend/app/modules/projects/details/controller.py @@ -0,0 +1,19 @@ +from app.context.manager import epoch_context +from app.modules.registry import get_services +from app.modules.projects.details.service.projects_details import ProjectsDetailsDTO + + +def get_projects_details(epoch: int, search_phrase: str): + context = epoch_context(epoch) + service = get_services(context.epoch_state).projects_details_service + + filtered_projects: ProjectsDetailsDTO = ( + service.get_projects_details_by_search_phrase(context, search_phrase) + ) + return [ + { + "name": project["name"], + "address": project["address"], + } + for project in filtered_projects.projects_details + ] diff --git a/backend/app/modules/projects/details/core.py b/backend/app/modules/projects/details/core.py new file mode 100644 index 0000000000..42d30dbd56 --- /dev/null +++ b/backend/app/modules/projects/details/core.py @@ -0,0 +1,20 @@ +from typing import List + +from app.infrastructure.database.models import ProjectsDetails + + +def filter_projects_details( + projects_details: List[ProjectsDetails], search_phrase: str +) -> List[ProjectsDetails]: + search_phrase = search_phrase.strip().lower() + + filtered_project_details = [] + + for project_details in projects_details: + if ( + search_phrase in project_details.name.lower() + or search_phrase in project_details.address.lower() + ): + filtered_project_details.append(project_details) + + return filtered_project_details diff --git a/backend/app/modules/projects/details/service/projects_details.py b/backend/app/modules/projects/details/service/projects_details.py new file mode 100644 index 0000000000..1993c128f3 --- /dev/null +++ b/backend/app/modules/projects/details/service/projects_details.py @@ -0,0 +1,35 @@ +from dataclasses import dataclass +from typing import List, Dict + +from app.pydantic import Model +from app.context.manager import Context +from app.infrastructure.database.projects_details import get_projects_details_for_epoch +from app.modules.projects.details.core import filter_projects_details + + +@dataclass +class ProjectsDetailsDTO: + projects_details: List[Dict[str, str]] + + +class StaticProjectsDetailsService(Model): + def get_projects_details_by_search_phrase( + self, context: Context, search_phrase: str + ) -> ProjectsDetailsDTO: + epoch = context.epoch_details.epoch_num + + projects_details = get_projects_details_for_epoch(epoch) + + filtered_projects_details = filter_projects_details( + projects_details, search_phrase + ) + + return ProjectsDetailsDTO( + projects_details=[ + { + "name": project.name, + "address": project.address, + } + for project in filtered_projects_details + ] + ) diff --git a/backend/migrations/__init__.py b/backend/migrations/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/backend/migrations/ipfs_integration/config.py b/backend/migrations/ipfs_integration/config.py new file mode 100644 index 0000000000..c9cc5b4669 --- /dev/null +++ b/backend/migrations/ipfs_integration/config.py @@ -0,0 +1,17 @@ +from enum import StrEnum + +from core import build_filename + + +class Config: + FILENAME_PREFIX = "ipfs_projects_details" + EPOCH = 4 # change corresponding to the epoch + JSON_FILEPATH = f"files/{build_filename(FILENAME_PREFIX, EPOCH)}" + CID = [ + "QmXomSdCCwt4FtBp3pidqSz3PtaiV2EyQikU6zRGWeCAsf" + ] # change corresponding to the epoch + GATEWAY_URL = "https://octant.infura-ipfs.io/ipfs/" + + +class ProjectDetails(StrEnum): + NAME = "name" diff --git a/backend/migrations/ipfs_integration/core.py b/backend/migrations/ipfs_integration/core.py new file mode 100644 index 0000000000..e1895f10bb --- /dev/null +++ b/backend/migrations/ipfs_integration/core.py @@ -0,0 +1,19 @@ +import re + + +def build_filename(prefix: str, epoch: int) -> str: + json_filename = f"{prefix}_epoch_{epoch}.json" + + return json_filename + + +def is_valid_ethereum_address(address: str) -> bool: + """ + Validates if the provided string is a valid Ethereum address. + + :param address: The address string to validate. + :return: True if valid, False otherwise. + """ + # Ethereum addresses are 42 characters long (including '0x') and hexadecimal + pattern = re.compile(r"^0x[a-fA-F0-9]{40}$") + return bool(pattern.match(address)) diff --git a/backend/migrations/ipfs_integration/files/cids.md b/backend/migrations/ipfs_integration/files/cids.md new file mode 100644 index 0000000000..cc3d54ad43 --- /dev/null +++ b/backend/migrations/ipfs_integration/files/cids.md @@ -0,0 +1,5 @@ +# CIDs in history +- Epoch1: QmSQEFD35gKxdPEmngNt1CWe3kSwiiGqBn1Z3FZvWb8mvK +- Epoch2: Qmds9N5y2vkMuPTD6M4EBxNXnf3bjTDmzWBGnCkQGsMMGe +- Epoch3: QmSXcT18anMXKACTueom8GXw8zrxTBbHGB71atitf6gZ9V +- Epoch4: QmXomSdCCwt4FtBp3pidqSz3PtaiV2EyQikU6zRGWeCAsf diff --git a/backend/migrations/ipfs_integration/files/ipfs_projects_details_epoch_1.json b/backend/migrations/ipfs_integration/files/ipfs_projects_details_epoch_1.json new file mode 100644 index 0000000000..0fb5c23963 --- /dev/null +++ b/backend/migrations/ipfs_integration/files/ipfs_projects_details_epoch_1.json @@ -0,0 +1,100 @@ +[ + [ + { + "name": "Ethereum Cat Herders", + "address": "0x02Cb3C150BEdca124d0aE8CcCb72fefbe705c953" + }, + { + "name": "Praise", + "address": "0x0B7246eF74Ca7b37Fdc3D15be4f0b49876622F95" + }, + { + "name": "RADAR Launch", + "address": "0x149D46eC060e75AE188876AdB6b24024637003C7" + }, + { + "name": "Tor Project", + "address": "0x15c941a44a343B8c46a28F2BB9aFc7a54E255A4f" + }, + { + "name": "DAO Drops", + "address": "0x1c01595f9534E33d411035AE99a4317faeC4f6Fe" + }, + { + "name": "OnionDAO", + "address": "0x20a1B17087482de88Fac6D7B5aE23A7175fd1395" + }, + { + "name": "Hypercerts", + "address": "0x2DCDF80f439843D7E0aD1fEF9E7a439B7917eAc9" + }, + { + "name": "MetaGame", + "address": "0x3455FbB4D34C6b47999B66c83aA7BD8FDDade638" + }, + { + "name": "PublicHAUS", + "address": "0x4A9a27d614a74Ee5524909cA27bdBcBB7eD3b315" + }, + { + "name": "Unitap", + "address": "0x4ADc8CC149A03F44386Bee80bab36F9e8022b195" + }, + { + "name": "Giveth", + "address": "0x6e8873085530406995170Da467010565968C7C62" + }, + { + "name": "BrightID", + "address": "0x78e084445C3F1006617e1f36794dd2261ecE4AE3" + }, + { + "name": "Kernel", + "address": "0x7DAC9Fc15C1Db4379D75A6E3f330aE849dFfcE18" + }, + { + "name": "Glo Dollar", + "address": "0x8c89a6bf53cCF63e7B4465Cc1b1330723B4BdcB7" + }, + { + "name": "Rotki", + "address": "0x9531C059098e3d194fF87FebB587aB07B30B1306" + }, + { + "name": "Clr.fund", + "address": "0xAb6D6a37c5110d1377832c451C33e4fA16A9BA05" + }, + { + "name": "BanklessDAO", + "address": "0xCf3efCE169acEC1B281C05E863F78acCF62BD944" + }, + { + "name": "EthStaker", + "address": "0xD165df4296C85e780509fa1eace0150d945d49Fd" + }, + { + "name": "GoodDollar", + "address": "0xF0652a820dd39EC956659E0018Da022132f2f40a" + }, + { + "name": "Protocol Guild", + "address": "0xF6CBDd6Ea6EC3C4359e33de0Ac823701Cc56C6c4" + }, + { + "name": "Drips", + "address": "0xcC7d34C76A9d08aa0109F7Bae35f29C1CE35355A" + }, + { + "name": "Pairwise", + "address": "0xd1B8dB70Ded72dB850713b2ce7e1A4FfAfAD95d1" + }, + { + "name": "Gitcoin", + "address": "0xde21F729137C5Af1b01d73aF1dC21eFfa2B8a0d6" + }, + { + "name": "Gravity DAO", + "address": "0xfFbD35255008F86322051F2313D4b343540e0e00" + } + ] +] \ No newline at end of file diff --git a/backend/migrations/ipfs_integration/files/ipfs_projects_details_epoch_2.json b/backend/migrations/ipfs_integration/files/ipfs_projects_details_epoch_2.json new file mode 100644 index 0000000000..8b373b2a63 --- /dev/null +++ b/backend/migrations/ipfs_integration/files/ipfs_projects_details_epoch_2.json @@ -0,0 +1,100 @@ +[ + [ + { + "name": "Ethereum Cat Herders", + "address": "0x02Cb3C150BEdca124d0aE8CcCb72fefbe705c953" + }, + { + "name": "Praise", + "address": "0x0B7246eF74Ca7b37Fdc3D15be4f0b49876622F95" + }, + { + "name": "L2BEAT", + "address": "0x0cbF31Ef6545EE30f47651D1A991Bf0aeB03DF29" + }, + { + "name": "Tor Project", + "address": "0x15c941a44a343B8c46a28F2BB9aFc7a54E255A4f" + }, + { + "name": "DAO Drops", + "address": "0x1c01595f9534E33d411035AE99a4317faeC4f6Fe" + }, + { + "name": "Hypercerts", + "address": "0x2DCDF80f439843D7E0aD1fEF9E7a439B7917eAc9" + }, + { + "name": "MetaGame", + "address": "0x3455FbB4D34C6b47999B66c83aA7BD8FDDade638" + }, + { + "name": "PublicHAUS", + "address": "0x4A9a27d614a74Ee5524909cA27bdBcBB7eD3b315" + }, + { + "name": "Funding the Commons", + "address": "0x576edCed7475D8F64a5e2D5227c93Ca57d7f5d20" + }, + { + "name": "Giveth", + "address": "0x6e8873085530406995170Da467010565968C7C62" + }, + { + "name": "BrightID", + "address": "0x78e084445C3F1006617e1f36794dd2261ecE4AE3" + }, + { + "name": "Kernel", + "address": "0x7DAC9Fc15C1Db4379D75A6E3f330aE849dFfcE18" + }, + { + "name": "Open Source Observer", + "address": "0x87fEEd6162CB7dFe6B62F64366742349bF4D1B05" + }, + { + "name": "Glo Dollar", + "address": "0x8c89a6bf53cCF63e7B4465Cc1b1330723B4BdcB7" + }, + { + "name": "Rotki", + "address": "0x9531C059098e3d194fF87FebB587aB07B30B1306" + }, + { + "name": "Clr.fund", + "address": "0xAb6D6a37c5110d1377832c451C33e4fA16A9BA05" + }, + { + "name": "Shielded Voting", + "address": "0xB476Ee7D610DAe7B23B671EBC7Bd6112E9772969" + }, + { + "name": "EthStaker", + "address": "0xD165df4296C85e780509fa1eace0150d945d49Fd" + }, + { + "name": "Token Engineering Commons", + "address": "0xE2f413190Bb5D6AAcB4A056F1B5E1fD5B8141045" + }, + { + "name": "Protocol Guild", + "address": "0xF6CBDd6Ea6EC3C4359e33de0Ac823701Cc56C6c4" + }, + { + "name": "Drips", + "address": "0xcC7d34C76A9d08aa0109F7Bae35f29C1CE35355A" + }, + { + "name": "Pairwise", + "address": "0xd1B8dB70Ded72dB850713b2ce7e1A4FfAfAD95d1" + }, + { + "name": "Gitcoin", + "address": "0xde21F729137C5Af1b01d73aF1dC21eFfa2B8a0d6" + }, + { + "name": "Revoke.cash", + "address": "0xe126b3E5d052f1F575828f61fEBA4f4f2603652a" + } + ] +] \ No newline at end of file diff --git a/backend/migrations/ipfs_integration/files/ipfs_projects_details_epoch_3.json b/backend/migrations/ipfs_integration/files/ipfs_projects_details_epoch_3.json new file mode 100644 index 0000000000..3c3ff16819 --- /dev/null +++ b/backend/migrations/ipfs_integration/files/ipfs_projects_details_epoch_3.json @@ -0,0 +1,124 @@ +[ + [ + { + "name": "StateOfEth", + "address": "0x0194325BF525Be0D4fBB0856894cEd74Da3B8356" + }, + { + "name": "Ethereum Cat Herders", + "address": "0x02Cb3C150BEdca124d0aE8CcCb72fefbe705c953" + }, + { + "name": "Praise", + "address": "0x0B7246eF74Ca7b37Fdc3D15be4f0b49876622F95" + }, + { + "name": "L2BEAT", + "address": "0x0cbF31Ef6545EE30f47651D1A991Bf0aeB03DF29" + }, + { + "name": "Tor Project", + "address": "0x15c941a44a343B8c46a28F2BB9aFc7a54E255A4f" + }, + { + "name": "DAO Drops", + "address": "0x1c01595f9534E33d411035AE99a4317faeC4f6Fe" + }, + { + "name": "Hypercerts", + "address": "0x2DCDF80f439843D7E0aD1fEF9E7a439B7917eAc9" + }, + { + "name": "MetaGame", + "address": "0x3455FbB4D34C6b47999B66c83aA7BD8FDDade638" + }, + { + "name": "Web3.js", + "address": "0x4C6fd545fc18C6538eC304Ae549717CA58f0D6eb" + }, + { + "name": "Boring Security", + "address": "0x52C45Bab6d0827F44a973899666D9Cd18Fd90bCF" + }, + { + "name": "Web3.py", + "address": "0x5597cD8d55D2Db56b10FF4F8fe69C8922BF6C537" + }, + { + "name": "Funding the Commons", + "address": "0x576edCed7475D8F64a5e2D5227c93Ca57d7f5d20" + }, + { + "name": "Giveth", + "address": "0x6e8873085530406995170Da467010565968C7C62" + }, + { + "name": "ReFi DAO", + "address": "0x7340F1a1e4e38F43d2FCC85cdb2b764de36B40c0" + }, + { + "name": "Gardens", + "address": "0x809C9f8dd8CA93A41c3adca4972Fa234C28F7714" + }, + { + "name": "Open Source Observer", + "address": "0x87fEEd6162CB7dFe6B62F64366742349bF4D1B05" + }, + { + "name": "Glo Dollar", + "address": "0x8c89a6bf53cCF63e7B4465Cc1b1330723B4BdcB7" + }, + { + "name": "GrowThePie", + "address": "0x9438b8B447179740cD97869997a2FCc9b4AA63a2" + }, + { + "name": "Rotki", + "address": "0x9531C059098e3d194fF87FebB587aB07B30B1306" + }, + { + "name": "MetaGov", + "address": "0x9be7267002CAD0b8501f7322d50612CB13788Bcf" + }, + { + "name": "NiceNode", + "address": "0x9cce47E9cF12C6147c9844adBB81fE85880c4df4" + }, + { + "name": "Shielded Voting", + "address": "0xB476Ee7D610DAe7B23B671EBC7Bd6112E9772969" + }, + { + "name": "Ethereum Attestation Service", + "address": "0xBCA48834b3653ec795411EB0FCBE4038F8527d62" + }, + { + "name": "ETH Daily", + "address": "0xEB40A065854bd90126A4E697aeA0976BA51b2eE7" + }, + { + "name": "EthStaker", + "address": "0xF01CEe26213d1A6eaF16422241AE81f7C17B9f98" + }, + { + "name": "Protocol Guild", + "address": "0xF6CBDd6Ea6EC3C4359e33de0Ac823701Cc56C6c4" + }, + { + "name": "Drips", + "address": "0xcC7d34C76A9d08aa0109F7Bae35f29C1CE35355A" + }, + { + "name": "Pairwise", + "address": "0xd1B8dB70Ded72dB850713b2ce7e1A4FfAfAD95d1" + }, + { + "name": "Gitcoin", + "address": "0xde21F729137C5Af1b01d73aF1dC21eFfa2B8a0d6" + }, + { + "name": "Revoke.cash", + "address": "0xe126b3E5d052f1F575828f61fEBA4f4f2603652a" + } + ] +] \ No newline at end of file diff --git a/backend/migrations/ipfs_integration/files/ipfs_projects_details_epoch_4.json b/backend/migrations/ipfs_integration/files/ipfs_projects_details_epoch_4.json new file mode 100644 index 0000000000..d28f6e8686 --- /dev/null +++ b/backend/migrations/ipfs_integration/files/ipfs_projects_details_epoch_4.json @@ -0,0 +1,124 @@ +[ + [ + { + "name": "BuidlGuidl", + "address": "0x00080706a7D99CBC163D52dcF435205B1aD940D1" + }, + { + "name": "Ethereum Cat Herders", + "address": "0x02Cb3C150BEdca124d0aE8CcCb72fefbe705c953" + }, + { + "name": "Praise", + "address": "0x0B7246eF74Ca7b37Fdc3D15be4f0b49876622F95" + }, + { + "name": "Vyper", + "address": "0x0c9dc7622aE5f56491aB4cCe060d6002450B79D2" + }, + { + "name": "L2BEAT", + "address": "0x0cbF31Ef6545EE30f47651D1A991Bf0aeB03DF29" + }, + { + "name": "Tor Project", + "address": "0x15c941a44a343B8c46a28F2BB9aFc7a54E255A4f" + }, + { + "name": "DAO Drops", + "address": "0x1c01595f9534E33d411035AE99a4317faeC4f6Fe" + }, + { + "name": "Blockscout", + "address": "0x242ba6d68FfEb4a098B591B32d370F973FF882B7" + }, + { + "name": "Hypercerts", + "address": "0x2DCDF80f439843D7E0aD1fEF9E7a439B7917eAc9" + }, + { + "name": "Protocol Guild", + "address": "0x3250c2CEE20FA34D1c4F68eAA87E53512e95A62a" + }, + { + "name": "Web3.js", + "address": "0x4C6fd545fc18C6538eC304Ae549717CA58f0D6eb" + }, + { + "name": "Dappnode", + "address": "0x53390590476dC98860316e4B46Bb9842AF55efc4" + }, + { + "name": "Web3.py", + "address": "0x5597cD8d55D2Db56b10FF4F8fe69C8922BF6C537" + }, + { + "name": "Funding the Commons", + "address": "0x576edCed7475D8F64a5e2D5227c93Ca57d7f5d20" + }, + { + "name": "TogetherCrew", + "address": "0x6612213880f80b298aB66375789E8Ef15e98604E" + }, + { + "name": "EcosynthesisX", + "address": "0x7380A42137D16a0E7684578d8b3d32e1fbD021B5" + }, + { + "name": "DeSci LATAM", + "address": "0x7Dd488f03E0A043b550E82D3C2685aA83B96407C" + }, + { + "name": "Gardens", + "address": "0x809C9f8dd8CA93A41c3adca4972Fa234C28F7714" + }, + { + "name": "Open Source Observer", + "address": "0x87fEEd6162CB7dFe6B62F64366742349bF4D1B05" + }, + { + "name": "growthepie", + "address": "0x9438b8B447179740cD97869997a2FCc9b4AA63a2" + }, + { + "name": "Rotki", + "address": "0x9531C059098e3d194fF87FebB587aB07B30B1306" + }, + { + "name": "NiceNode", + "address": "0x9cce47E9cF12C6147c9844adBB81fE85880c4df4" + }, + { + "name": "Shielded Voting", + "address": "0xB476Ee7D610DAe7B23B671EBC7Bd6112E9772969" + }, + { + "name": "Ethereum Attestation Service", + "address": "0xBCA48834b3653ec795411EB0FCBE4038F8527d62" + }, + { + "name": "EthStaker", + "address": "0xF01CEe26213d1A6eaF16422241AE81f7C17B9f98" + }, + { + "name": "PizzaDAO", + "address": "0xF41a98D4F2E52aa1ccB48F0b6539e955707b8F7a" + }, + { + "name": "Abundance Protocol", + "address": "0xc6FD734790E83820e311211B6d9A682BCa4ac97b" + }, + { + "name": "Pairwise", + "address": "0xd1B8dB70Ded72dB850713b2ce7e1A4FfAfAD95d1" + }, + { + "name": "Revoke.cash", + "address": "0xe126b3E5d052f1F575828f61fEBA4f4f2603652a" + }, + { + "name": "Trustful", + "address": "0xf7253A0E87E39d2cD6365919D4a3D56D431D0041" + } + ] +] diff --git a/backend/migrations/ipfs_integration/logic.py b/backend/migrations/ipfs_integration/logic.py new file mode 100644 index 0000000000..af677bd9af --- /dev/null +++ b/backend/migrations/ipfs_integration/logic.py @@ -0,0 +1,127 @@ +import json +import os +from typing import List, Optional, Dict + +import requests +from bs4 import BeautifulSoup + +from config import Config, ProjectDetails +from core import is_valid_ethereum_address + + +def get_addresses_from_cid(cid: str) -> List[str]: + """ + Retrieves a list of Ethereum addresses from the CID by parsing the HTML content. + + :param cid: The CID to fetch. + :param gateway_url: The base URL of the IPFS gateway. + :return: A list of Ethereum addresses found under the CID. + """ + gateway_url = Config.GATEWAY_URL + url = f"{gateway_url}{cid}/" # Ensure the trailing slash + try: + response = requests.get(url) + response.raise_for_status() + html_content = response.text + + soup = BeautifulSoup(html_content, "html.parser") + addresses = [] + for link in soup.find_all("a"): + href = link.get("href") + if href and href not in ("../", "?", ""): + address = os.path.basename(href) + address = address.strip("/") + + if is_valid_ethereum_address(address): + addresses.append(address) + return addresses + except requests.exceptions.RequestException as e: + print(f"Error fetching CID {cid}: {e}") + return [] + + +def get_json_data_from_address( + cid: str, address: str, gateway_url: str = Config.GATEWAY_URL +) -> Optional[str]: + """ + Fetches the JSON data from the given Ethereum address under the CID. + + :param cid: The CID under which the address resides. + :param address: The Ethereum address (file name) to fetch. + :param gateway_url: The base URL of the IPFS gateway. + :return: A dictionary containing the JSON data, or None if an error occurs. + """ + url = f"{gateway_url}{cid}/{address}" + try: + response = requests.get(url) + response.raise_for_status() + content_type = response.headers.get("Content-Type", "") + + if "application/json" in content_type: + json_data = response.json() + else: + json_data = json.loads(response.text) + return json_data + except requests.exceptions.RequestException as e: + print(f"Error fetching address {address} under CID {cid}: {e}") + return None + except json.JSONDecodeError as e: + print(f"Error decoding JSON from address {address} under CID {cid}: {e}") + return None + + +def extract_details_from_json( + json_data: Dict, *, details_to_extract: List[ProjectDetails] +) -> Dict: + """ + Extracts the 'name' field from the JSON data. + + :param json_data: The JSON data dictionary. + :return: The 'name' value, or None if not found. + """ + output = {} + for detail_to_extract in details_to_extract: + detail_to_extract = detail_to_extract.value + output[detail_to_extract] = json_data.get(detail_to_extract) + return output + + +def main(): + all_projects_details = [] + + for cid in Config.CID: + print(f"\nProcessing CID: {cid}") + addresses = get_addresses_from_cid(cid) + if not addresses: + print(f"No Ethereum addresses found under CID {cid}.") + continue + + projects_details = [] + for address in addresses: + json_data = get_json_data_from_address(cid, address) + + if not json_data: + print(f"Failed to retrieve JSON data for address: {address}") + continue + + project_details = extract_details_from_json( + json_data, details_to_extract=[ProjectDetails.NAME] + ) + project_details["address"] = address + + name = project_details.get(ProjectDetails.NAME.value) + projects_details.append(project_details) + + print(f"Project name for address {address}: {name}") + + print(f"Number of projects for {cid}", len(projects_details)) + all_projects_details.append(projects_details) + + print(f"All projects details that are saved in a JSON file: {all_projects_details}") + + with open(Config.JSON_FILEPATH, "w") as f: + json.dump(all_projects_details, f, indent=4) + + +if __name__ == "__main__": + main() diff --git a/backend/migrations/ipfs_integration/migration_helpers.py b/backend/migrations/ipfs_integration/migration_helpers.py new file mode 100644 index 0000000000..433a31caf1 --- /dev/null +++ b/backend/migrations/ipfs_integration/migration_helpers.py @@ -0,0 +1,53 @@ +import json +import os +from datetime import datetime +from typing import Dict +from alembic import op + + +def _load_json_data(filepath: str) -> Dict: + with open(filepath, "r") as file: + data = json.load(file) + return data[0] + + +def _get_json_path(filename: str) -> str: + json_filename = os.path.join(os.path.dirname(__file__), "files", filename) + return json_filename + + +def _prepare_project_upsert_query(project: dict, epoch: int) -> str: + current_time = datetime.utcnow() + + return f""" + INSERT INTO project_details (name, address, created_at, epoch) + VALUES ('{project["name"]}', '{project["address"]}', '{current_time}', {epoch}) + ON CONFLICT (address, epoch) + DO UPDATE SET + name = EXCLUDED.name, + created_at = '{current_time}'; + """ + + +def _prepare_delete_project_query(project: dict, epoch: int) -> str: + return f""" + DELETE FROM project_details WHERE address = '{project["address"]}' AND epoch = {epoch} + """ + + +def upgrade(filename: str, epoch: int): + json_filepath = _get_json_path(filename) + project_data = _load_json_data(json_filepath) + + for project in project_data: + query = _prepare_project_upsert_query(project, epoch) + op.execute(query) + + +def downgrade(filename: str, epoch: int): + json_filepath = _get_json_path(filename) + project_data = _load_json_data(json_filepath) + + for project in project_data: + query = _prepare_delete_project_query(project, epoch) + op.execute(query) diff --git a/backend/migrations/versions/87b2cefcfa11_add_projectsdetails_table.py b/backend/migrations/versions/87b2cefcfa11_add_projectsdetails_table.py new file mode 100644 index 0000000000..823fa45ec9 --- /dev/null +++ b/backend/migrations/versions/87b2cefcfa11_add_projectsdetails_table.py @@ -0,0 +1,32 @@ +"""Add ProjectsDetails table + +Revision ID: 87b2cefcfa11 +Revises: 8b425b454a86 +Create Date: 2024-09-20 11:12:33.753739 + +""" +from alembic import op +import sqlalchemy as sa + + +revision = "87b2cefcfa11" +down_revision = "8b425b454a86" +branch_labels = None +depends_on = None + + +def upgrade(): + op.create_table( + "project_details", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("address", sa.String(length=42), nullable=False), + sa.Column("name", sa.String(), nullable=False), + sa.Column("epoch", sa.Integer(), nullable=False), + sa.Column("created_at", sa.TIMESTAMP(), nullable=True), + sa.PrimaryKeyConstraint("id"), + sa.UniqueConstraint("address", "epoch", name="uq_address_epoch"), + ) + + +def downgrade(): + op.drop_table("project_details") diff --git a/backend/migrations/versions/c34003767fa8_add_projects_details_from_ipfs_for_.py b/backend/migrations/versions/c34003767fa8_add_projects_details_from_ipfs_for_.py new file mode 100644 index 0000000000..45519e4156 --- /dev/null +++ b/backend/migrations/versions/c34003767fa8_add_projects_details_from_ipfs_for_.py @@ -0,0 +1,28 @@ +"""Add projects details from IPFS for Epoch 1, 2, 3 and 4 + +Revision ID: c34003767fa8 +Revises: 87b2cefcfa11 +Create Date: 2024-09-20 11:14:31.965331 + +""" +from migrations.ipfs_integration import migration_helpers as ipfs_migration + +revision = "c34003767fa8" +down_revision = "87b2cefcfa11" +branch_labels = None +depends_on = None + +FILENAME = "ipfs_projects_details_epoch_{}.json" +EPOCHS = (1, 2, 3, 4) + + +def upgrade(): + for epoch in EPOCHS: + filename = FILENAME.format(epoch) + ipfs_migration.upgrade(filename, epoch) + + +def downgrade(): + for epoch in EPOCHS: + filename = FILENAME.format(epoch) + ipfs_migration.downgrade(filename, epoch) diff --git a/backend/tests/modules/projects/conftest.py b/backend/tests/modules/projects/conftest.py new file mode 100644 index 0000000000..6ac250455e --- /dev/null +++ b/backend/tests/modules/projects/conftest.py @@ -0,0 +1,11 @@ +import pytest + +from tests.modules.projects.helpers import sample_projects_details + + +@pytest.fixture(scope="function") +def patch_projects_details(monkeypatch): + monkeypatch.setattr( + "app.modules.projects.details.service.projects_details.get_projects_details_for_epoch", + sample_projects_details, + ) diff --git a/backend/tests/modules/projects/details/test_filtering_core.py b/backend/tests/modules/projects/details/test_filtering_core.py new file mode 100644 index 0000000000..26fc1f7e6c --- /dev/null +++ b/backend/tests/modules/projects/details/test_filtering_core.py @@ -0,0 +1,71 @@ +from app.modules.projects.details.core import filter_projects_details +from tests.modules.projects.helpers import sample_projects_details + +SAMPLE_PROJECTS_DETAILS = sample_projects_details() + + +def test_filter_projects_partial_match_in_name(): + """Test that partial matches in the project name are correctly filtered.""" + search_phrase = "Octant" + filtered_projects = filter_projects_details(SAMPLE_PROJECTS_DETAILS, search_phrase) + + assert len(filtered_projects) == 2 + assert filtered_projects[0].name == "OctantProject3" + assert filtered_projects[1].name == "OctantTestProject4" + + +def test_filter_projects_partial_match_in_address(): + """Test that partial matches in the project address are correctly filtered.""" + search_phrase = "0x111" + filtered_projects = filter_projects_details(SAMPLE_PROJECTS_DETAILS, search_phrase) + + assert len(filtered_projects) == 1 + assert filtered_projects[0].address == "0x111" + assert filtered_projects[0].name == "TEST1" + + +def test_filter_projects_empty_search_phrase(): + """Test that all projects are returned when search phrase is empty.""" + search_phrase = "" + filtered_projects = filter_projects_details(SAMPLE_PROJECTS_DETAILS, search_phrase) + + assert len(filtered_projects) == len(SAMPLE_PROJECTS_DETAILS) + assert all(project in filtered_projects for project in SAMPLE_PROJECTS_DETAILS) + + +def test_filter_projects_special_characters_in_search_phrase(): + """Test that search phrases with special characters work correctly.""" + search_phrase = "0x444" + filtered_projects = filter_projects_details(SAMPLE_PROJECTS_DETAILS, search_phrase) + + assert len(filtered_projects) == 1 + assert filtered_projects[0].address == "0x444" + assert filtered_projects[0].name == "OctantTestProject4" + + +def test_filter_projects_search_phrase_not_in_name_or_address(): + """Test that no projects are returned when search phrase doesn't match anything.""" + search_phrase = "NonExistentProject" + filtered_projects = filter_projects_details(SAMPLE_PROJECTS_DETAILS, search_phrase) + + assert len(filtered_projects) == 0 + + +def test_filter_projects_multiple_matches(): + """Test that multiple projects are returned when search phrase matches multiple names/addresses.""" + search_phrase = "Project" + filtered_projects = filter_projects_details(SAMPLE_PROJECTS_DETAILS, search_phrase) + + assert len(filtered_projects) == 2 + assert filtered_projects[0].name == "OctantProject3" + assert filtered_projects[1].name == "OctantTestProject4" + + +def test_filter_projects_whitespace_in_search_phrase(): + """Test that leading/trailing whitespace in search phrase is handled correctly.""" + search_phrase = " TEST1 " + filtered_projects = filter_projects_details(SAMPLE_PROJECTS_DETAILS, search_phrase) + + assert len(filtered_projects) == 1 + assert filtered_projects[0].name == "TEST1" + assert filtered_projects[0].address == "0x111" diff --git a/backend/tests/modules/projects/details/test_projects_filtering.py b/backend/tests/modules/projects/details/test_projects_filtering.py new file mode 100644 index 0000000000..8216ffabfb --- /dev/null +++ b/backend/tests/modules/projects/details/test_projects_filtering.py @@ -0,0 +1,34 @@ +import pytest + +from app.modules.projects.details.service.projects_details import ( + StaticProjectsDetailsService, + ProjectsDetailsDTO, +) +from tests.helpers.context import get_context + + +@pytest.fixture(autouse=True) +def before(app, patch_projects_details): + pass + + +@pytest.mark.parametrize("search_phrase", ["Octant", "TEST", "AnyName"]) +def test_get_projects_details_by_search_phrase(search_phrase): + context = get_context(4) + service = StaticProjectsDetailsService() + projects_details: ProjectsDetailsDTO = ( + service.get_projects_details_by_search_phrase(context, search_phrase) + ) + + for project in projects_details.projects_details: + assert search_phrase.lower() in project["name"].lower() + + +def test_get_projects_details_by_search_phrase_not_found(): + context = get_context(4) + service = StaticProjectsDetailsService() + projects_details: ProjectsDetailsDTO = ( + service.get_projects_details_by_search_phrase(context, "NOT_FOUND") + ) + + assert projects_details.projects_details == [] diff --git a/backend/tests/modules/projects/helpers.py b/backend/tests/modules/projects/helpers.py new file mode 100644 index 0000000000..8ed3b97201 --- /dev/null +++ b/backend/tests/modules/projects/helpers.py @@ -0,0 +1,12 @@ +from typing import List + +from app.infrastructure.database.models import ProjectsDetails + + +def sample_projects_details(*args, **kwargs) -> List[ProjectsDetails]: + return [ + ProjectsDetails(id=1, address="0x111", name="TEST1", epoch=4), + ProjectsDetails(id=2, address="0x222", name="AnyName2", epoch=4), + ProjectsDetails(id=3, address="0x333", name="OctantProject3", epoch=4), + ProjectsDetails(id=4, address="0x444", name="OctantTestProject4", epoch=4), + ] diff --git a/backend/tests/modules/projects/metadata/test_projects_retrieval.py b/backend/tests/modules/projects/metadata/test_projects_retrieval.py index 9f91b87041..dc585372f0 100644 --- a/backend/tests/modules/projects/metadata/test_projects_retrieval.py +++ b/backend/tests/modules/projects/metadata/test_projects_retrieval.py @@ -14,7 +14,6 @@ def before(app, patch_projects): MOCK_PROJECTS.get_project_cid.return_value = ( "QmXbFKrMGJUbXupmTQsQhoy9zkzXDBHZkPAzKC4yiaLt5n" ) - pass def test_get_projects_metadata_epoch_1(): From 6e7608be1a3d92066249e29837f48b3be5ac40aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Zi=C3=B3=C5=82ek?= Date: Tue, 24 Sep 2024 23:48:51 +0200 Subject: [PATCH 125/241] fix: remove autofocus --- client/src/views/ProjectsView/ProjectsView.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/client/src/views/ProjectsView/ProjectsView.tsx b/client/src/views/ProjectsView/ProjectsView.tsx index 2e85efdb91..80dbda2722 100644 --- a/client/src/views/ProjectsView/ProjectsView.tsx +++ b/client/src/views/ProjectsView/ProjectsView.tsx @@ -144,6 +144,7 @@ const ProjectsView = (): ReactElement => { onChange={onChangeSearchQuery} onClear={() => setSearchQuery('')} placeholder={t('searchInputPlaceholder')} + shouldAutoFocusAndSelect={false} value={searchQuery} variant="search" /> From 99206e8b483380da7824d4a6c423508dfdcb1be8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Miko=C5=82ajczyk?= Date: Wed, 25 Sep 2024 13:32:43 +0200 Subject: [PATCH 126/241] oct-1971: client refactor 2 --- client/public/images/funds_swept.webp | Bin 63720 -> 0 bytes client/public/images/lock-glm-desktop.webp | Bin 31868 -> 0 bytes client/public/images/lock-glm-mobile.webp | Bin 14840 -> 0 bytes .../images/modalEffectiveLockedBalance.webp | Bin 2710 -> 0 bytes client/public/images/tip-connect-wallet.webp | Bin 1734 -> 0 bytes client/public/images/tip-withdraw.webp | Bin 5988 -> 0 bytes .../HomeGridCurrentGlmLock.tsx | 1 - .../LockGlmBudgetBox.module.scss | 27 ---- .../LockGlmNotification.tsx | 6 +- .../DonationsListSkeletonItem.module.scss | 3 - .../HomeGridDonations.module.scss | 12 -- .../HomeGridPersonalAllocation.tsx | 8 +- .../WithdrawEth/WithdrawEth.module.scss | 11 -- .../WithdrawEth/WithdrawEth.tsx | 2 +- .../HomeGridRewardsEstimator.module.scss | 12 -- ...ardsEstimatorEpochDaysSelector.module.scss | 3 +- .../HomeGridTransactions.module.scss | 12 -- .../HomeGridTransactions.tsx | 2 +- .../MetricsDonationsProgressBar/index.tsx | 2 - .../Metrics/MetricsEpoch/MetricsEpoch.tsx | 33 +---- ...ricsEpochDonationsProgressBar.module.scss} | 0 .../MetricsEpochDonationsProgressBar.tsx} | 8 +- .../index.tsx | 2 + .../types.ts | 2 +- .../MetricsEpochGridBelowThreshold.tsx | 90 -------------- .../MetricsEpochGridBelowThreshold/index.tsx | 2 - .../MetricsEpochGridBelowThreshold/types.ts | 5 - .../MetricsEpochGridDonationsVsMatching.tsx | 4 +- ...pochGridDonationsVsPersonalAllocations.tsx | 4 +- .../SettingsMainInfoBox.module.scss | 12 -- .../SettingsMainInfoBox.tsx | 2 +- .../shared/Layout/Layout.module.scss | 116 ------------------ .../LayoutWallet/LayoutWallet.module.scss | 18 --- ...LayoutWalletPersonalAllocation.module.scss | 20 --- .../ModalLayoutConnectWallet.tsx | 2 +- 35 files changed, 21 insertions(+), 400 deletions(-) delete mode 100644 client/public/images/funds_swept.webp delete mode 100644 client/public/images/lock-glm-desktop.webp delete mode 100644 client/public/images/lock-glm-mobile.webp delete mode 100644 client/public/images/modalEffectiveLockedBalance.webp delete mode 100644 client/public/images/tip-connect-wallet.webp delete mode 100644 client/public/images/tip-withdraw.webp delete mode 100644 client/src/components/Metrics/MetricsDonationsProgressBar/index.tsx rename client/src/components/Metrics/{MetricsDonationsProgressBar/MetricsDonationsProgressBar.module.scss => MetricsEpoch/MetricsEpochDonationsProgressBar/MetricsEpochDonationsProgressBar.module.scss} (100%) rename client/src/components/Metrics/{MetricsDonationsProgressBar/MetricsDonationsProgressBar.tsx => MetricsEpoch/MetricsEpochDonationsProgressBar/MetricsEpochDonationsProgressBar.tsx} (86%) create mode 100644 client/src/components/Metrics/MetricsEpoch/MetricsEpochDonationsProgressBar/index.tsx rename client/src/components/Metrics/{MetricsDonationsProgressBar => MetricsEpoch/MetricsEpochDonationsProgressBar}/types.ts (61%) delete mode 100644 client/src/components/Metrics/MetricsEpoch/MetricsEpochGridBelowThreshold/MetricsEpochGridBelowThreshold.tsx delete mode 100644 client/src/components/Metrics/MetricsEpoch/MetricsEpochGridBelowThreshold/index.tsx delete mode 100644 client/src/components/Metrics/MetricsEpoch/MetricsEpochGridBelowThreshold/types.ts diff --git a/client/public/images/funds_swept.webp b/client/public/images/funds_swept.webp deleted file mode 100644 index f7b3fc1c115a1df074c5edb1f628e2f2718753cd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 63720 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 diff --git a/client/public/images/lock-glm-desktop.webp b/client/public/images/lock-glm-desktop.webp deleted file mode 100644 index 3aeb02a0b6a8d3031d253f6dc0831326148ce799..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 31868 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 j;_`J{b&)Sb3 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 L5r%B{7kvI7DbM~* diff --git a/client/public/images/lock-glm-mobile.webp b/client/public/images/lock-glm-mobile.webp deleted file mode 100644 index da185519a18ac555408dbafc6861bc9197befe1b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14840 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 diff --git a/client/public/images/modalEffectiveLockedBalance.webp b/client/public/images/modalEffectiveLockedBalance.webp deleted file mode 100644 index 746d0205c352b7a6c6d8bdddbbc33f11d420b6b6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2710 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 diff --git a/client/public/images/tip-withdraw.webp b/client/public/images/tip-withdraw.webp deleted file mode 100644 index e0ce55c11690358f778b7666f3dfb18d04fc24b6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5988 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 S=S>5u=Xbn2tU=-Ti~<1F)`!>t diff --git a/client/src/components/Home/HomeGridCurrentGlmLock/HomeGridCurrentGlmLock.tsx b/client/src/components/Home/HomeGridCurrentGlmLock/HomeGridCurrentGlmLock.tsx index f81599ea23..25f58f1579 100644 --- a/client/src/components/Home/HomeGridCurrentGlmLock/HomeGridCurrentGlmLock.tsx +++ b/client/src/components/Home/HomeGridCurrentGlmLock/HomeGridCurrentGlmLock.tsx @@ -82,7 +82,6 @@ const HomeGridCurrentGlmLock: FC = ({ className }) dataTest: 'TooltipEffectiveLockedBalance', position: 'bottom-right', text: t('tooltipText'), - tooltipClassName: styles.effectiveTooltip, }, }, ]} diff --git a/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmBudgetBox/LockGlmBudgetBox.module.scss b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmBudgetBox/LockGlmBudgetBox.module.scss index 8762572265..aafbfc59d3 100644 --- a/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmBudgetBox/LockGlmBudgetBox.module.scss +++ b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmBudgetBox/LockGlmBudgetBox.module.scss @@ -1,29 +1,3 @@ -.transactionHash { - display: flex; - justify-content: center; - margin: 1rem 0 0 0; -} - -.availableFunds { - height: 3.2rem; - display: flex; - align-items: flex-end; - border-top: 0.1rem solid $color-white; - margin-top: 1.6rem; - - .value { - color: $color-black; - - &.isError { - color: $color-octant-orange; - } - } -} - -.button { - padding: 0; -} - .budgetRow { position: relative; height: 5.6rem; @@ -50,7 +24,6 @@ width: 10rem; } - .budgetLabel { color: $color-octant-grey5; } diff --git a/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmNotification/LockGlmNotification.tsx b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmNotification/LockGlmNotification.tsx index 1d2a362a29..4e857801d6 100644 --- a/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmNotification/LockGlmNotification.tsx +++ b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmNotification/LockGlmNotification.tsx @@ -53,11 +53,7 @@ const LockGlmNotification: FC = ({ isVertical >

- +
{label &&
{label}
} {text && ( diff --git a/client/src/components/Home/HomeGridDonations/DonationsListSkeletonItem/DonationsListSkeletonItem.module.scss b/client/src/components/Home/HomeGridDonations/DonationsListSkeletonItem/DonationsListSkeletonItem.module.scss index b11934a8e5..be193215bf 100644 --- a/client/src/components/Home/HomeGridDonations/DonationsListSkeletonItem/DonationsListSkeletonItem.module.scss +++ b/client/src/components/Home/HomeGridDonations/DonationsListSkeletonItem/DonationsListSkeletonItem.module.scss @@ -26,9 +26,6 @@ @include skeleton(); height: 1.5rem; width: 4rem; - } - - .value { margin-left: auto; } } diff --git a/client/src/components/Home/HomeGridDonations/HomeGridDonations.module.scss b/client/src/components/Home/HomeGridDonations/HomeGridDonations.module.scss index 3ce8ff6bfd..7a26d25787 100644 --- a/client/src/components/Home/HomeGridDonations/HomeGridDonations.module.scss +++ b/client/src/components/Home/HomeGridDonations/HomeGridDonations.module.scss @@ -1,18 +1,6 @@ .root { padding: 0rem 2.4rem 2.4rem; overflow: auto; - - .divider { - margin-top: 2rem; - width: 100%; - height: 0.1rem; - background-color: $color-octant-grey1; - } - - .withdrawEthButton { - margin-top: 2.4rem; - width: 100%; - } } .titleWrapper { diff --git a/client/src/components/Home/HomeGridPersonalAllocation/HomeGridPersonalAllocation.tsx b/client/src/components/Home/HomeGridPersonalAllocation/HomeGridPersonalAllocation.tsx index 5b542d74c8..74f0919265 100644 --- a/client/src/components/Home/HomeGridPersonalAllocation/HomeGridPersonalAllocation.tsx +++ b/client/src/components/Home/HomeGridPersonalAllocation/HomeGridPersonalAllocation.tsx @@ -80,11 +80,9 @@ const HomeGridPersonalAllocation: FC = ({ class tooltipProps: { position: 'bottom-right', text: ( -
-
- {t('pendingFundsAvailableAfter')} -
-
+
+
{t('pendingFundsAvailableAfter')}
+
{/* TODO OCT-1041 fetch next epoch props instead of assuming the same length */} {currentEpochProps && timeCurrentEpochStart && timeCurrentAllocationEnd ? format( diff --git a/client/src/components/Home/HomeGridPersonalAllocation/ModalWithdrawEth/WithdrawEth/WithdrawEth.module.scss b/client/src/components/Home/HomeGridPersonalAllocation/ModalWithdrawEth/WithdrawEth/WithdrawEth.module.scss index 067ce0ebaf..3bfdb2167a 100644 --- a/client/src/components/Home/HomeGridPersonalAllocation/ModalWithdrawEth/WithdrawEth/WithdrawEth.module.scss +++ b/client/src/components/Home/HomeGridPersonalAllocation/ModalWithdrawEth/WithdrawEth/WithdrawEth.module.scss @@ -3,17 +3,6 @@ height: 100%; } -.inputs { - display: flex; - justify-content: space-between; - align-items: flex-end; - width: 100%; -} - -.input { - @include flexBasisGutter(2, 1.6rem); -} - .button { width: 100%; margin: 2.4rem 0; diff --git a/client/src/components/Home/HomeGridPersonalAllocation/ModalWithdrawEth/WithdrawEth/WithdrawEth.tsx b/client/src/components/Home/HomeGridPersonalAllocation/ModalWithdrawEth/WithdrawEth/WithdrawEth.tsx index 776661b9ac..f489c977d1 100644 --- a/client/src/components/Home/HomeGridPersonalAllocation/ModalWithdrawEth/WithdrawEth/WithdrawEth.tsx +++ b/client/src/components/Home/HomeGridPersonalAllocation/ModalWithdrawEth/WithdrawEth/WithdrawEth.tsx @@ -74,7 +74,7 @@ const WithdrawEth: FC = ({ onCloseModal }) => { return (
- + )} - ) : null} + )}
); }; diff --git a/client/src/components/Home/HomeGridEpochResults/EpochResultsDetails/types.ts b/client/src/components/Home/HomeGridEpochResults/EpochResultsDetails/types.ts index 7b49c2abf6..a7a3b15f05 100644 --- a/client/src/components/Home/HomeGridEpochResults/EpochResultsDetails/types.ts +++ b/client/src/components/Home/HomeGridEpochResults/EpochResultsDetails/types.ts @@ -2,4 +2,5 @@ import { ProjectIpfsWithRewards } from 'hooks/queries/useProjectsIpfsWithRewards export default interface EpochResultsDetailsProps { details?: ProjectIpfsWithRewards & { epoch: number }; + isLoading?: boolean; } diff --git a/client/src/components/Home/HomeGridEpochResults/HomeGridEpochResults.tsx b/client/src/components/Home/HomeGridEpochResults/HomeGridEpochResults.tsx index df0d11df32..5132e4388b 100644 --- a/client/src/components/Home/HomeGridEpochResults/HomeGridEpochResults.tsx +++ b/client/src/components/Home/HomeGridEpochResults/HomeGridEpochResults.tsx @@ -1,6 +1,6 @@ import cx from 'classnames'; import _first from 'lodash/first'; -import React, { FC, useState } from 'react'; +import React, { FC, useEffect, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import GridTile from 'components/shared/Grid/GridTile'; @@ -9,6 +9,7 @@ import useIsProjectAdminMode from 'hooks/helpers/useIsProjectAdminMode'; import useCurrentEpoch from 'hooks/queries/useCurrentEpoch'; import useIsDecisionWindowOpen from 'hooks/queries/useIsDecisionWindowOpen'; import useIsPatronMode from 'hooks/queries/useIsPatronMode'; +import useProjectsIpfsWithRewards from 'hooks/queries/useProjectsIpfsWithRewards'; import { arrowRight } from 'svg/misc'; import EpochResults from './EpochResults'; @@ -16,17 +17,48 @@ import styles from './HomeGridEpochResults.module.scss'; import HomeGridEpochResultsProps from './types'; const HomeGridEpochResults: FC = ({ className }) => { - const { data: currentEpoch } = useCurrentEpoch(); + const initalLoadingRef = useRef(true); const { data: isDecisionWindowOpen } = useIsDecisionWindowOpen(); + const { data: currentEpoch } = useCurrentEpoch(); const [epoch, setEpoch] = useState(currentEpoch! - 1); const { t } = useTranslation('translation', { keyPrefix: 'components.home.homeGridEpochResults', }); + const { data: projectsIpfsWithRewards, isFetching: isFetchingProjectsIpfsWithRewards } = + useProjectsIpfsWithRewards(epoch); const isProjectAdminMode = useIsProjectAdminMode(); const { data: isPatronMode } = useIsPatronMode(); - const isRightArrowDisabled = epoch === currentEpoch! - 1; - const isLeftArrowDisabled = epoch < 2; + const projects = + projectsIpfsWithRewards.map(props => ({ + epoch, + ...props, + })) || []; + + const isAnyProjectDonated = projects.some(({ donations }) => donations > 0n); + + const isLoading = isFetchingProjectsIpfsWithRewards && !isAnyProjectDonated; + + const isRightArrowDisabled = + (isLoading && initalLoadingRef.current) || epoch === currentEpoch! - 1; + const isLeftArrowDisabled = (isLoading && initalLoadingRef.current) || epoch < 2; + + useEffect(() => { + if (!isDecisionWindowOpen || isLoading || epoch !== currentEpoch! - 1 || isAnyProjectDonated) { + return; + } + + setEpoch(prev => prev - 1); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isLoading]); + + useEffect(() => { + if ((initalLoadingRef.current && isLoading) || !initalLoadingRef.current) { + return; + } + + initalLoadingRef.current = false; + }, [isLoading]); return ( = ({ className }) => { } >
- +
); diff --git a/client/src/locales/en/translation.json b/client/src/locales/en/translation.json index ef1f375955..de25fc5945 100644 --- a/client/src/locales/en/translation.json +++ b/client/src/locales/en/translation.json @@ -224,7 +224,8 @@ "donationsShort": "D", "matchingShort": "M", "totalShort": "T", - "clickToVisitProject": "Click to visit project" + "clickToVisitProject": "Click to visit project", + "loadingChartData": "Loading chart data " } }, "settings": { From afa1c90761fc2111a9ac12f47a4f11516d794394 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Zi=C3=B3=C5=82ek?= Date: Thu, 26 Sep 2024 09:15:29 +0200 Subject: [PATCH 129/241] fix: buttons to use Inter, line-height 100% --- client/src/styles/index.scss | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/client/src/styles/index.scss b/client/src/styles/index.scss index b563f71512..ca9da57df8 100644 --- a/client/src/styles/index.scss +++ b/client/src/styles/index.scss @@ -1,11 +1,15 @@ @import './fonts.css'; -:root, input { +:root, input, button { // Direct input to prevent useragent override. font-family: 'Inter', sans-serif; font-weight: 400; } +button { + line-height: 100%; +} + * { margin: 0; padding: 0; From 71429b854257634dcf865857a29796fdf382a24c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Zi=C3=B3=C5=82ek?= Date: Thu, 26 Sep 2024 09:27:43 +0200 Subject: [PATCH 130/241] feat: remove tiptiles --- .../AllocationTipTiles.module.scss | 20 -- .../AllocationTipTiles/AllocationTipTiles.tsx | 124 --------- .../Allocation/AllocationTipTiles/index.tsx | 2 - .../Allocation/AllocationTipTiles/types.ts | 3 - client/src/components/Settings/Settings.tsx | 6 +- .../SettingsShowTipsBox.tsx | 25 -- .../Settings/SettingsShowTipsBox/index.tsx | 2 - .../shared/TipTile/TipTile.module.scss | 108 -------- .../src/components/shared/TipTile/TipTile.tsx | 105 -------- .../src/components/shared/TipTile/index.tsx | 2 - client/src/components/shared/TipTile/types.ts | 14 - client/src/constants/localStorageKeys.ts | 42 --- .../src/hooks/helpers/useAppConnectManager.ts | 5 - client/src/hooks/helpers/useAppIsLoading.ts | 5 - .../src/hooks/helpers/useAppPopulateState.ts | 20 +- client/src/locales/en/translation.json | 31 +-- .../src/services/localStorageService.test.ts | 35 --- client/src/services/localStorageService.ts | 43 --- client/src/store/settings/store.test.ts | 25 -- client/src/store/settings/store.ts | 11 - client/src/store/settings/types.ts | 2 - client/src/store/tips/store.test.ts | 250 ------------------ client/src/store/tips/store.ts | 102 ------- client/src/store/tips/types.ts | 23 -- .../src/views/ProjectsView/ProjectsView.tsx | 26 -- 25 files changed, 6 insertions(+), 1025 deletions(-) delete mode 100644 client/src/components/Allocation/AllocationTipTiles/AllocationTipTiles.module.scss delete mode 100644 client/src/components/Allocation/AllocationTipTiles/AllocationTipTiles.tsx delete mode 100644 client/src/components/Allocation/AllocationTipTiles/index.tsx delete mode 100644 client/src/components/Allocation/AllocationTipTiles/types.ts delete mode 100644 client/src/components/Settings/SettingsShowTipsBox/SettingsShowTipsBox.tsx delete mode 100644 client/src/components/Settings/SettingsShowTipsBox/index.tsx delete mode 100644 client/src/components/shared/TipTile/TipTile.module.scss delete mode 100644 client/src/components/shared/TipTile/TipTile.tsx delete mode 100644 client/src/components/shared/TipTile/index.tsx delete mode 100644 client/src/components/shared/TipTile/types.ts delete mode 100644 client/src/store/tips/store.test.ts delete mode 100644 client/src/store/tips/store.ts delete mode 100644 client/src/store/tips/types.ts diff --git a/client/src/components/Allocation/AllocationTipTiles/AllocationTipTiles.module.scss b/client/src/components/Allocation/AllocationTipTiles/AllocationTipTiles.module.scss deleted file mode 100644 index 46ac8138a2..0000000000 --- a/client/src/components/Allocation/AllocationTipTiles/AllocationTipTiles.module.scss +++ /dev/null @@ -1,20 +0,0 @@ -.bold { - color: $color-octant-green; - font-weight: $font-weight-bold; -} - -.uqTooLowImage { - height: 7.2rem; - - @media #{$desktop-up} { - height: 10rem; - } -} - -.rewardsImage { - height: 8.5rem; - - @media #{$desktop-up} { - height: 11rem; - } -} diff --git a/client/src/components/Allocation/AllocationTipTiles/AllocationTipTiles.tsx b/client/src/components/Allocation/AllocationTipTiles/AllocationTipTiles.tsx deleted file mode 100644 index 07ff1f506e..0000000000 --- a/client/src/components/Allocation/AllocationTipTiles/AllocationTipTiles.tsx +++ /dev/null @@ -1,124 +0,0 @@ -import React, { FC, Fragment, useEffect } from 'react'; -import { Trans, useTranslation } from 'react-i18next'; -import { useNavigate } from 'react-router-dom'; -import { useAccount } from 'wagmi'; - -import TipTile from 'components/shared/TipTile'; -import useMediaQuery from 'hooks/helpers/useMediaQuery'; -import useRefreshAntisybilStatus from 'hooks/mutations/useRefreshAntisybilStatus'; -import useCurrentEpoch from 'hooks/queries/useCurrentEpoch'; -import useIndividualReward from 'hooks/queries/useIndividualReward'; -import useIsDecisionWindowOpen from 'hooks/queries/useIsDecisionWindowOpen'; -import useUqScore from 'hooks/queries/useUqScore'; -import useUserAllocations from 'hooks/queries/useUserAllocations'; -import { ROOT_ROUTES } from 'routes/RootRoutes/routes'; -import useTipsStore from 'store/tips/store'; - -import styles from './AllocationTipTiles.module.scss'; -import AllocationTipTilesProps from './types'; - -const AllocationTipTiles: FC = ({ className }) => { - const { t } = useTranslation('translation', { keyPrefix: 'components.allocation.tip' }); - const navigate = useNavigate(); - const { isDesktop } = useMediaQuery(); - const { address, isConnected } = useAccount(); - const { - mutateAsync: refreshAntisybilStatus, - isPending: isPendingRefreshAntisybilStatus, - isSuccess: isSuccessRefreshAntisybilStatus, - error: refreshAntisybilStatusError, - } = useRefreshAntisybilStatus(); - const { data: currentEpoch } = useCurrentEpoch(); - const { data: isDecisionWindowOpen } = useIsDecisionWindowOpen(); - const { data: individualReward, isFetching: isFetchingIndividualReward } = useIndividualReward(); - const { data: userAllocations, isFetching: isFetchingUserAllocation } = useUserAllocations(); - const { data: uqScore, isFetching: isFetchingUqScore } = useUqScore(currentEpoch!, { - enabled: - isSuccessRefreshAntisybilStatus || - (refreshAntisybilStatusError as null | { message: string })?.message === - 'Address is already used for delegation', - }); - const { - wasRewardsAlreadyClosed, - setWasRewardsAlreadyClosed, - wasUqTooLowAlreadyClosed, - setWasUqTooLowAlreadyClosed, - } = useTipsStore(state => ({ - setWasConnectWalletAlreadyClosed: state.setWasConnectWalletAlreadyClosed, - setWasRewardsAlreadyClosed: state.setWasRewardsAlreadyClosed, - setWasUqTooLowAlreadyClosed: state.setWasUqTooLowAlreadyClosed, - wasConnectWalletAlreadyClosed: state.data.wasConnectWalletAlreadyClosed, - wasRewardsAlreadyClosed: state.data.wasRewardsAlreadyClosed, - wasUqTooLowAlreadyClosed: state.data.wasUqTooLowAlreadyClosed, - })); - - useEffect(() => { - if (!address) { - return; - } - /** - * The initial value of UQ for every user is 0.2. - * It does not update automatically after delegation nor after change in Gitcoin Passport itself. - * - * We need to refreshAntisybilStatus to force BE to refetch current values from Gitcoin Passport - * and return true value. - */ - refreshAntisybilStatus(address!); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - const isEpoch1 = currentEpoch === 1; - - const isUqTooLowTipVisible = - !!isDecisionWindowOpen && - !isPendingRefreshAntisybilStatus && - !isFetchingUqScore && - uqScore === 20n && - !wasUqTooLowAlreadyClosed; - - const isRewardsTipVisible = - !isEpoch1 && - isConnected && - !isFetchingIndividualReward && - !!individualReward && - individualReward !== 0n && - !isFetchingUserAllocation && - !userAllocations?.hasUserAlreadyDoneAllocation && - !!isDecisionWindowOpen && - !wasRewardsAlreadyClosed; - - return ( - - navigate(ROOT_ROUTES.settings.absolute)} - onClose={() => setWasUqTooLowAlreadyClosed(true)} - text={ - ]} - i18nKey={ - isDesktop - ? 'components.allocation.tip.uqTooLow.text.desktop' - : 'components.allocation.tip.uqTooLow.text.mobile' - } - /> - } - title={isDesktop ? t('uqTooLow.title.desktop') : t('uqTooLow.title.mobile')} - /> - setWasRewardsAlreadyClosed(true)} - text={isDesktop ? t('rewards.text.desktop') : t('rewards.text.mobile')} - title={t('rewards.title')} - /> - - ); -}; - -export default AllocationTipTiles; diff --git a/client/src/components/Allocation/AllocationTipTiles/index.tsx b/client/src/components/Allocation/AllocationTipTiles/index.tsx deleted file mode 100644 index c3a59a07cc..0000000000 --- a/client/src/components/Allocation/AllocationTipTiles/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line no-restricted-exports -export { default } from './AllocationTipTiles'; diff --git a/client/src/components/Allocation/AllocationTipTiles/types.ts b/client/src/components/Allocation/AllocationTipTiles/types.ts deleted file mode 100644 index 6ce64c2c9c..0000000000 --- a/client/src/components/Allocation/AllocationTipTiles/types.ts +++ /dev/null @@ -1,3 +0,0 @@ -export default interface AllocationTipTilesProps { - className?: string; -} diff --git a/client/src/components/Settings/Settings.tsx b/client/src/components/Settings/Settings.tsx index 1a3c455c2d..d4b35b0217 100644 --- a/client/src/components/Settings/Settings.tsx +++ b/client/src/components/Settings/Settings.tsx @@ -7,7 +7,6 @@ import SettingsCurrencyBox from 'components/Settings/SettingsCurrencyBox'; import SettingsMainInfoBox from 'components/Settings/SettingsMainInfoBox'; import SettingsPatronModeBox from 'components/Settings/SettingsPatronModeBox'; import SettingsShowOnboardingBox from 'components/Settings/SettingsShowOnboardingBox'; -import SettingsShowTipsBox from 'components/Settings/SettingsShowTipsBox'; import useIsProjectAdminMode from 'hooks/helpers/useIsProjectAdminMode'; import useIsPatronMode from 'hooks/queries/useIsPatronMode'; @@ -33,10 +32,7 @@ const Settings = (): ReactElement => { {isConnected && !isProjectAdminMode && } {!isProjectAdminMode && !isPatronMode && ( - <> - - - + )}
diff --git a/client/src/components/Settings/SettingsShowTipsBox/SettingsShowTipsBox.tsx b/client/src/components/Settings/SettingsShowTipsBox/SettingsShowTipsBox.tsx deleted file mode 100644 index 9d906d8c92..0000000000 --- a/client/src/components/Settings/SettingsShowTipsBox/SettingsShowTipsBox.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import React, { ReactElement } from 'react'; -import { useTranslation } from 'react-i18next'; - -import SettingsToggleBox from 'components/Settings/SettingsToggleBox'; -import useSettingsStore from 'store/settings/store'; - -const SettingsShowTipsBox = (): ReactElement => { - const { t } = useTranslation('translation', { keyPrefix: 'views.settings' }); - const { setAreOctantTipsAlwaysVisible, areOctantTipsAlwaysVisible } = useSettingsStore(state => ({ - areOctantTipsAlwaysVisible: state.data.areOctantTipsAlwaysVisible, - setAreOctantTipsAlwaysVisible: state.setAreOctantTipsAlwaysVisible, - })); - - return ( - setAreOctantTipsAlwaysVisible(event.target.checked)} - > - {t('alwaysShowOctantTips')} - - ); -}; - -export default SettingsShowTipsBox; diff --git a/client/src/components/Settings/SettingsShowTipsBox/index.tsx b/client/src/components/Settings/SettingsShowTipsBox/index.tsx deleted file mode 100644 index 55c60a1397..0000000000 --- a/client/src/components/Settings/SettingsShowTipsBox/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line no-restricted-exports -export { default } from './SettingsShowTipsBox'; diff --git a/client/src/components/shared/TipTile/TipTile.module.scss b/client/src/components/shared/TipTile/TipTile.module.scss deleted file mode 100644 index f2db86de87..0000000000 --- a/client/src/components/shared/TipTile/TipTile.module.scss +++ /dev/null @@ -1,108 +0,0 @@ -.root { - position: relative; - display: flex; - background: $color-white; - border-radius: $border-radius-16; - width: 100%; - height: 20rem; - align-items: center; - padding: 0 2.4rem; - - &.isClickable { - cursor: pointer; - } - - @media #{$desktop-up} { - height: 22.4rem; - } -} - -.content { - display: flex; - flex-direction: column; - width: 100%; -} - -.info { - display: flex; - align-items: center; - height: 5.6rem; - border-bottom: 0.1rem solid $color-octant-grey3; -} - -.infoLabel { - color: $color-octant-grey5; - font-size: $font-size-12; - margin-left: 0.8rem; - line-height: 3.2rem; - - @media #{$desktop-up} { - font-size: $font-size-14; - } -} - -.imageWrapper { - display: flex; - flex: 1; - justify-content: center; - max-height: 14.4rem; - - @media #{$desktop-up} { - align-items: center; - width: 28rem; - } -} - -.image { - max-width: 100%; - max-height: 100%; -} - -.body { - display: flex; - text-align: left; - height: 14.4rem; - padding-top: 1.2rem; - - @media #{$desktop-up} { - height: 16.8rem; - } -} - -.titleAndText { - display: flex; - flex-direction: column; -} - -.title { - color: $color-octant-dark; - font-weight: $font-weight-bold; - font-size: $font-size-16; - line-height: 3.2rem; - - @media #{$desktop-up} { - font-size: $font-size-18; - line-height: 4.8rem; - } -} - -.text { - color: $color-octant-grey5; - font-size: $font-size-12; - font-weight: $font-weight-semibold; - line-height: 2rem; - width: 20rem; - - @media #{$desktop-up} { - flex: 1; - width: 33.6rem; - font-size: $font-size-14; - line-height: 2.2rem; - } -} - -.buttonClose { - position: absolute; - top: 0.8rem; - right: 0.8rem; -} diff --git a/client/src/components/shared/TipTile/TipTile.tsx b/client/src/components/shared/TipTile/TipTile.tsx deleted file mode 100644 index 745db0fccc..0000000000 --- a/client/src/components/shared/TipTile/TipTile.tsx +++ /dev/null @@ -1,105 +0,0 @@ -import cx from 'classnames'; -import { AnimatePresence, motion } from 'framer-motion'; -import React, { useRef } from 'react'; -import { useTranslation } from 'react-i18next'; - -import Button from 'components/ui/Button'; -import Img from 'components/ui/Img'; -import Svg from 'components/ui/Svg'; -import useIsProjectAdminMode from 'hooks/helpers/useIsProjectAdminMode'; -import useMediaQuery from 'hooks/helpers/useMediaQuery'; -import { cross, info } from 'svg/misc'; - -import styles from './TipTile.module.scss'; -import { TipTileProps } from './types'; - -const TipTile: React.FC = ({ - className, - dataTest = 'TipTile', - image, - imageClassName, - isOpen, - onClose, - onClick, - text, - title, -}) => { - const { t } = useTranslation('translation', { keyPrefix: 'common' }); - const { isDesktop } = useMediaQuery(); - const isProjectAdminMode = useIsProjectAdminMode(); - const shouldSkipEntranceAnimation = useRef(isOpen); - - return ( - - {!isProjectAdminMode && isOpen && ( - 1 - (1 - x) ** 3, - mass: 1.5, - stiffness: 800, - }} - > -
-
- -
{t('gettingStarted')}
-
-
-
-
{title}
-
{text}
-
-
- -
-
-
-
); }; diff --git a/client/src/components/Home/HomeGridTransactions/TransactionLabel/TransactionLabel.tsx b/client/src/components/Home/HomeGridTransactions/TransactionLabel/TransactionLabel.tsx index 3caecf673f..4197e10207 100644 --- a/client/src/components/Home/HomeGridTransactions/TransactionLabel/TransactionLabel.tsx +++ b/client/src/components/Home/HomeGridTransactions/TransactionLabel/TransactionLabel.tsx @@ -15,7 +15,7 @@ const TransactionLabel: FC = ({ isFinalized, isMultisig } return t('pendingMultisig'); } if (isFinalized) { - return t('confirmed'); + return i18n.t('common.confirmed'); } return i18n.t('common.pending'); // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/client/src/components/Settings/Settings.tsx b/client/src/components/Settings/Settings.tsx index d4b35b0217..7918c7d313 100644 --- a/client/src/components/Settings/Settings.tsx +++ b/client/src/components/Settings/Settings.tsx @@ -31,9 +31,7 @@ const Settings = (): ReactElement => { {isConnected && !isProjectAdminMode && } - {!isProjectAdminMode && !isPatronMode && ( - - )} + {!isProjectAdminMode && !isPatronMode && }
); diff --git a/client/src/locales/en/translation.json b/client/src/locales/en/translation.json index 0488d0e028..74a83a6f3b 100644 --- a/client/src/locales/en/translation.json +++ b/client/src/locales/en/translation.json @@ -52,7 +52,8 @@ "lockedGLM": "Locked GLM", "unlockedGLM": "Unlocked GLM", "total": "Total", - "matching": "Matching" + "matching": "Matching", + "confirmed": "Confirmed" }, "components": { "allocation": { @@ -73,8 +74,7 @@ "standard": "{{sum}} / {{threshold}}", "simulate": "Your impact {{value}}", "simulateLoading": "Calculating..." - }, - "allocationSuccessful": "Allocation successful" + } }, "shared": { "onboardingStepper": { @@ -190,7 +190,6 @@ "epochDonation": "Epoch {{epoch}} donation" }, "transactionLabel": { - "confirmed": "Confirmed", "pendingMultisig": "Pending multisig" } }, @@ -491,4 +490,4 @@ "information": "We're synchronizing things to prepare the
next epoch, so the app will be unavailable
for a little while. Please check back soon." } } -} +} \ No newline at end of file diff --git a/client/src/services/toastService.ts b/client/src/services/toastService.ts index e97dfd8d83..daa25b02c8 100644 --- a/client/src/services/toastService.ts +++ b/client/src/services/toastService.ts @@ -4,7 +4,6 @@ import ToastProps from 'components/ui/Toast/types'; import triggerToast from 'utils/triggerToast'; export const TOAST_NAMES = [ - 'allocationSuccessful', 'backendError', 'changeNetwork', 'confirmChanges', From 6d156165bf94e50835146f9ef64e830c21151969 Mon Sep 17 00:00:00 2001 From: Housekeeper Bot Date: Fri, 27 Sep 2024 08:51:34 +0000 Subject: [PATCH 143/241] [CI/CD] Update uat.env contracts --- ci/argocd/contracts/uat.env | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/ci/argocd/contracts/uat.env b/ci/argocd/contracts/uat.env index e08a716201..17c6428778 100644 --- a/ci/argocd/contracts/uat.env +++ b/ci/argocd/contracts/uat.env @@ -1,8 +1,8 @@ -BLOCK_NUMBER=6725985 +BLOCK_NUMBER=6768112 GLM_CONTRACT_ADDRESS=0x71432DD1ae7DB41706ee6a22148446087BdD0906 -AUTH_CONTRACT_ADDRESS=0x8aa3e15BBA708d46B5e3Bd786A899b054260ECDC -DEPOSITS_CONTRACT_ADDRESS=0x0db12DE3E3f6887A4308BBC81b873f0D3c08D62F -EPOCHS_CONTRACT_ADDRESS=0xfec3fCa4bE643d961dCd327C411d90DF4E0F12B8 -PROPOSALS_CONTRACT_ADDRESS=0xB41D5fc5D9059127e5256Bbb77Df9d25d0373372 -WITHDRAWALS_TARGET_CONTRACT_ADDRESS=0x881D5A2003e8ba59189e50eBe1E9dEf1F8d1C469 -VAULT_CONTRACT_ADDRESS=0x821Dfe7AE82d2cEC385c00D5F785c74f5434cE3A +AUTH_CONTRACT_ADDRESS=0x06CDA8700151efDF0C5d97DCb2fA69e202E8c2c5 +DEPOSITS_CONTRACT_ADDRESS=0x1C6aA9F059E6d8C9dC3771E8544EEd44Ab0fEE73 +EPOCHS_CONTRACT_ADDRESS=0xB16F3fcEE044d6fa56385dDAC822eF8Ff1f1785c +PROPOSALS_CONTRACT_ADDRESS=0x7e948e67364965d88f150E11cf172267385ab858 +WITHDRAWALS_TARGET_CONTRACT_ADDRESS=0x394138d77Ea17C5E34297599Ec44035b6d38029F +VAULT_CONTRACT_ADDRESS=0xFe8358a2EdbA6E8048D61c24DC21dae2Bd42A786 From af991a031f3277878be0e4c5632c5186afce041a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Zi=C3=B3=C5=82ek?= Date: Fri, 27 Sep 2024 11:00:47 +0200 Subject: [PATCH 144/241] fix: wrong epoch in home view --- .../HomeGridUQScoreAddresses/HomeGridUQScoreAddresses.tsx | 5 +++-- client/src/components/Home/HomeRewards/HomeRewards.tsx | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/client/src/components/Home/HomeGridUQScore/HomeGridUQScoreAddresses/HomeGridUQScoreAddresses.tsx b/client/src/components/Home/HomeGridUQScore/HomeGridUQScoreAddresses/HomeGridUQScoreAddresses.tsx index 1870de2dd4..42c0fb80fe 100644 --- a/client/src/components/Home/HomeGridUQScore/HomeGridUQScoreAddresses/HomeGridUQScoreAddresses.tsx +++ b/client/src/components/Home/HomeGridUQScore/HomeGridUQScoreAddresses/HomeGridUQScoreAddresses.tsx @@ -43,6 +43,7 @@ const HomeGridUQScoreAddresses: FC = ({ isFetchin return [delegationPrimaryAddress, delegationSecondaryAddress]; } + // Can return [undefined]. return [accountAddress]; }, [delegationPrimaryAddress, delegationSecondaryAddress, isDelegationCompleted, accountAddress]); @@ -89,8 +90,8 @@ const HomeGridUQScoreAddresses: FC = ({ isFetchin return (
- {addresses.map(address => ( -
+ {addresses?.map((address, index) => ( +
{!isConnected || address === '0x???' ? ( ) : ( diff --git a/client/src/components/Home/HomeRewards/HomeRewards.tsx b/client/src/components/Home/HomeRewards/HomeRewards.tsx index cffa2e9f9a..fa5ddc8b90 100644 --- a/client/src/components/Home/HomeRewards/HomeRewards.tsx +++ b/client/src/components/Home/HomeRewards/HomeRewards.tsx @@ -1,5 +1,5 @@ import cx from 'classnames'; -import React, { ReactNode, useMemo } from 'react'; +import React, { ReactElement, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { useAccount } from 'wagmi'; @@ -17,7 +17,7 @@ import useMatchedProjectRewards from 'hooks/queries/useMatchedProjectRewards'; import styles from './HomeRewards.module.scss'; -const HomeRewards = (): ReactNode => { +const HomeRewards = (): ReactElement => { const { i18n, t } = useTranslation('translation', { keyPrefix: 'components.home.homeRewards' }); const { address, isConnected } = useAccount(); const { data: individualReward, isFetching: isFetchingIndividualReward } = useIndividualReward(); From 1c1ce9282b66d0d504c93b882dbde46d4497124f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Zi=C3=B3=C5=82ek?= Date: Fri, 27 Sep 2024 11:52:11 +0200 Subject: [PATCH 145/241] feat: adjust border radius --- .../components/Home/HomeGridDonations/HomeGridDonations.tsx | 2 +- client/src/components/ui/Button/Button.module.scss | 6 +++++- client/src/components/ui/Button/types.ts | 1 + 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/client/src/components/Home/HomeGridDonations/HomeGridDonations.tsx b/client/src/components/Home/HomeGridDonations/HomeGridDonations.tsx index 93b09933b6..aef7f49226 100644 --- a/client/src/components/Home/HomeGridDonations/HomeGridDonations.tsx +++ b/client/src/components/Home/HomeGridDonations/HomeGridDonations.tsx @@ -67,7 +67,7 @@ const HomeGridDonations: FC = ({ className }) => { setCurrentView('edit'); setIsAllocationDrawerOpen(!isAllocationDrawerOpen); }} - variant="cta" + variant="cta2" > {i18n.t('common.edit')} diff --git a/client/src/components/ui/Button/Button.module.scss b/client/src/components/ui/Button/Button.module.scss index eeaeec1bec..13a28b6e7a 100644 --- a/client/src/components/ui/Button/Button.module.scss +++ b/client/src/components/ui/Button/Button.module.scss @@ -49,7 +49,7 @@ $paddingVertical: 1rem; } .variant-- { - &cta { + &cta, &cta2 { color: $color-white; border: 0.1rem solid $color-octant-dark; background: $color-octant-dark; @@ -61,6 +61,10 @@ $paddingVertical: 1rem; } } + &cta2 { + border-radius: $border-radius-10; + } + &secondary { color: $color-octant-dark; border: 0.1rem solid $color-octant-dark; diff --git a/client/src/components/ui/Button/types.ts b/client/src/components/ui/Button/types.ts index a2f56ecb76..809d4f4b63 100644 --- a/client/src/components/ui/Button/types.ts +++ b/client/src/components/ui/Button/types.ts @@ -5,6 +5,7 @@ export const BUTTON_VARIANTS = [ 'secondary', 'secondary2', 'cta', + 'cta2', 'iconOnly', 'iconOnly2', 'iconOnlyTransparent', From 47f9c2fe9b4912eabe82c5814b3f67d365964531 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Zi=C3=B3=C5=82ek?= Date: Fri, 27 Sep 2024 12:16:20 +0200 Subject: [PATCH 146/241] feat: UQ tile hover states --- .../Home/HomeGridUQScore/HomeGridUQScore.tsx | 11 ++++++----- client/src/components/ui/Button/Button.module.scss | 10 +++++++++- client/src/components/ui/Button/Button.tsx | 5 +++-- client/src/components/ui/Button/types.ts | 2 ++ 4 files changed, 20 insertions(+), 8 deletions(-) diff --git a/client/src/components/Home/HomeGridUQScore/HomeGridUQScore.tsx b/client/src/components/Home/HomeGridUQScore/HomeGridUQScore.tsx index 5024a4c72a..fb9e0939e2 100644 --- a/client/src/components/Home/HomeGridUQScore/HomeGridUQScore.tsx +++ b/client/src/components/Home/HomeGridUQScore/HomeGridUQScore.tsx @@ -241,12 +241,13 @@ const HomeGridUQScore: FC = ({ className }) => { className={className} title={t('yourUniquenessScore')} titleSuffix={ -
setIsCalculatingYourUniquenessModalOpen(true)} - > - {t('whatIsThis')} -
+ variant="link3" + /> } >
@@ -255,7 +256,7 @@ const HomeGridUQScore: FC = ({ className }) => { className={styles.visitDashboard} href={GITCOIN_PASSPORT_CUSTOM_OCTANT_DASHBOARD} label={t('scoreTooLow')} - variant="link" + variant="link6" />
); diff --git a/client/src/components/shared/Layout/LayoutTopBar/LayoutTopBar.tsx b/client/src/components/shared/Layout/LayoutTopBar/LayoutTopBar.tsx index 2b6f279e22..87df79bd21 100644 --- a/client/src/components/shared/Layout/LayoutTopBar/LayoutTopBar.tsx +++ b/client/src/components/shared/Layout/LayoutTopBar/LayoutTopBar.tsx @@ -7,20 +7,19 @@ import { useAccount } from 'wagmi'; import Allocation from 'components/Allocation'; import Settings from 'components/Settings'; +import LayoutTopBarCalendar from 'components/shared/Layout/LayoutTopBarCalendar'; import Button from 'components/ui/Button'; import Drawer from 'components/ui/Drawer'; import Svg from 'components/ui/Svg'; import useIsProjectAdminMode from 'hooks/helpers/useIsProjectAdminMode'; import useMediaQuery from 'hooks/helpers/useMediaQuery'; import useNavigationTabs from 'hooks/helpers/useNavigationTabs'; -import useCurrentEpoch from 'hooks/queries/useCurrentEpoch'; -import useIsDecisionWindowOpen from 'hooks/queries/useIsDecisionWindowOpen'; import useIsPatronMode from 'hooks/queries/useIsPatronMode'; import { ROOT_ROUTES } from 'routes/RootRoutes/routes'; import useAllocationsStore from 'store/allocations/store'; import useLayoutStore from 'store/layout/store'; import { octant } from 'svg/logo'; -import { calendar, chevronBottom } from 'svg/misc'; +import { chevronBottom } from 'svg/misc'; import { allocate, settings } from 'svg/navigation'; import truncateEthAddress from 'utils/truncateEthAddress'; @@ -33,8 +32,6 @@ const LayoutTopBar: FC = ({ className }) => { const { isConnected, address } = useAccount(); const { pathname } = useLocation(); const navigate = useNavigate(); - const { data: isDecisionWindowOpen } = useIsDecisionWindowOpen(); - const { data: currentEpoch } = useCurrentEpoch(); const { isSettingsDrawerOpen, isAllocationDrawerOpen, @@ -60,21 +57,6 @@ const LayoutTopBar: FC = ({ className }) => { const tabs = useNavigationTabs(true); const [scope, animate] = useAnimate(); - const allocationInfoText = useMemo(() => { - const epoch = currentEpoch! - 1; - - if (isDecisionWindowOpen) { - return isMobile - ? t('epochAllocationWindowOpenShort', { epoch }) - : t('epochAllocationWindowOpen', { epoch }); - } - - return isMobile - ? t('epochAllocationWindowClosedShort', { epoch }) - : t('epochAllocationWindowClosed', { epoch }); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isDecisionWindowOpen, currentEpoch, isMobile]); - const buttonWalletText = useMemo(() => { if (!isConnected) { return !isMobile ? t('connectWallet') : t('connect'); @@ -159,10 +141,7 @@ const LayoutTopBar: FC = ({ className }) => { ))}
)} -
- {!isMobile && } - {allocationInfoText} -
+
); diff --git a/client/src/hooks/queries/useRewardsRate.ts b/client/src/hooks/queries/useRewardsRate.ts new file mode 100644 index 0000000000..bbe28dd4a4 --- /dev/null +++ b/client/src/hooks/queries/useRewardsRate.ts @@ -0,0 +1,17 @@ +import { UseQueryOptions, UseQueryResult, useQuery } from '@tanstack/react-query'; + +import { apiGetRewardsRate, Response } from 'api/calls/rewardsRate'; +import { QUERY_KEYS } from 'api/queryKeys'; + +export default function useRewardsRate( + epoch: number | undefined, + options?: UseQueryOptions, +): UseQueryResult { + return useQuery({ + enabled: !!epoch, + queryFn: ({ signal }) => apiGetRewardsRate(epoch!, signal), + queryKey: QUERY_KEYS.rewardsRate(epoch!), + select: response => response.rewardsRate * 100, + ...options, + }); +} From 4b825b5595f258de9bb0ca118e60847ae70a8e56 Mon Sep 17 00:00:00 2001 From: Pawel Peregud Date: Mon, 30 Sep 2024 10:45:34 +0200 Subject: [PATCH 157/241] drop unused functionality --- backend/app/modules/user/antisybil/core.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/backend/app/modules/user/antisybil/core.py b/backend/app/modules/user/antisybil/core.py index 7bdb03ecf8..f4cf044a82 100644 --- a/backend/app/modules/user/antisybil/core.py +++ b/backend/app/modules/user/antisybil/core.py @@ -1,5 +1,5 @@ import json -from typing import Optional, Tuple +from typing import Optional from app.modules.user.antisybil.dto import AntisybilStatusDTO from app.constants import ( @@ -23,7 +23,7 @@ def determine_antisybil_score( if score is None: return None - (_, potential_score) = _apply_gtc_staking_stamp_nullification(score.score, score) + potential_score = _apply_gtc_staking_stamp_nullification(score.score, score) if user_address in TIMEOUT_LIST: return AntisybilStatusDTO( @@ -55,9 +55,7 @@ def _has_guest_stamp_applied_by_gp(score: GPStamps) -> bool: return len(stamps) > 0 -def _apply_gtc_staking_stamp_nullification( - score: int, stamps: GPStamps -) -> Tuple[bool, int]: +def _apply_gtc_staking_stamp_nullification(score: int, stamps: GPStamps) -> int: "Take score and stamps as returned by Passport and remove score associated with GTC staking" delta = 0 all_stamps = json.loads(stamps.stamps) @@ -65,4 +63,4 @@ def _apply_gtc_staking_stamp_nullification( for provider in providers: if provider in GTC_STAKING_STAMP_PROVIDERS_AND_SCORES.keys(): delta = delta + GTC_STAKING_STAMP_PROVIDERS_AND_SCORES[provider] - return (delta > 0, score - delta) + return score - delta From 09f900931c7464ff08f20463f44a2b83b84f5f22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Miko=C5=82ajczyk?= Date: Mon, 30 Sep 2024 12:58:35 +0200 Subject: [PATCH 158/241] oct-1980: tablet calendar --- .../Calendar/Calendar.module.scss | 6 ++--- .../Calendar/Calendar.tsx | 24 +++++++++---------- .../CalendarItem/CalendarItem.module.scss | 2 +- .../LayoutTopBarCalendar.module.scss | 6 ++++- .../LayoutTopBarCalendar.tsx | 6 ++--- 5 files changed, 23 insertions(+), 21 deletions(-) diff --git a/client/src/components/shared/Layout/LayoutTopBarCalendar/Calendar/Calendar.module.scss b/client/src/components/shared/Layout/LayoutTopBarCalendar/Calendar/Calendar.module.scss index 88ae032e71..1062a837cd 100644 --- a/client/src/components/shared/Layout/LayoutTopBarCalendar/Calendar/Calendar.module.scss +++ b/client/src/components/shared/Layout/LayoutTopBarCalendar/Calendar/Calendar.module.scss @@ -8,7 +8,7 @@ max-height: 50vh; padding: 0.1rem 0 0; - @media #{$desktop-up} { + @media #{$tablet-up} { max-height: auto; padding: 1.6rem; } @@ -19,7 +19,7 @@ flex-direction: column; width: 100%; - @media #{$desktop-up} { + @media #{$tablet-up} { flex-direction: row; width: auto; } @@ -30,7 +30,7 @@ display: flex; flex-direction: column; - @media #{$desktop-up} { + @media #{$tablet-up} { flex-direction: row; } } diff --git a/client/src/components/shared/Layout/LayoutTopBarCalendar/Calendar/Calendar.tsx b/client/src/components/shared/Layout/LayoutTopBarCalendar/Calendar/Calendar.tsx index b997d09450..b8d3da9d91 100644 --- a/client/src/components/shared/Layout/LayoutTopBarCalendar/Calendar/Calendar.tsx +++ b/client/src/components/shared/Layout/LayoutTopBarCalendar/Calendar/Calendar.tsx @@ -13,7 +13,7 @@ let isInitialResizeDone = false; const Calendar = (): ReactElement => { const constraintsRef = useRef(null); const milestonesWrapperRef = useRef(null); - const { isTablet, isMobile } = useMediaQuery(); + const { isMobile } = useMediaQuery(); const x = useMotionValue(0); const y = useMotionValue(0); @@ -55,23 +55,21 @@ const Calendar = (): ReactElement => { } = constraintsRef.current!.getBoundingClientRect(); const { height: milestonesWrapperHeight, width: milestonesWrapperWidth } = milestonesWrapperRef.current!.getBoundingClientRect(); - const motionValue = - isMobile || isTablet - ? y.get() + (elTop > containerTop ? -elTop + containerTop : 0) - : x.get() + (elLeft > containerLeft ? -elLeft + containerLeft : 0); - const maxMotionValue = - isMobile || isTablet - ? milestonesWrapperHeight - containerHeight - : milestonesWrapperWidth - containerWidth; + const motionValue = isMobile + ? y.get() + (elTop > containerTop ? -elTop + containerTop : 0) + : x.get() + (elLeft > containerLeft ? -elLeft + containerLeft : 0); + const maxMotionValue = isMobile + ? milestonesWrapperHeight - containerHeight + : milestonesWrapperWidth - containerWidth; const motionValueToSet = motionValue < -maxMotionValue ? -maxMotionValue : motionValue; - if (isMobile || isTablet) { + if (isMobile) { y.set(motionValueToSet); } else { x.set(motionValueToSet); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isMobile, isTablet]); + }, [isMobile]); useEffect(() => { if (!milestonesWrapperRef.current) { @@ -99,9 +97,9 @@ const Calendar = (): ReactElement => { {milestonesWithIsActive.map(({ id, ...milestone }) => ( diff --git a/client/src/components/shared/Layout/LayoutTopBarCalendar/CalendarItem/CalendarItem.module.scss b/client/src/components/shared/Layout/LayoutTopBarCalendar/CalendarItem/CalendarItem.module.scss index cae932b9b7..b1e7f236f1 100644 --- a/client/src/components/shared/Layout/LayoutTopBarCalendar/CalendarItem/CalendarItem.module.scss +++ b/client/src/components/shared/Layout/LayoutTopBarCalendar/CalendarItem/CalendarItem.module.scss @@ -14,7 +14,7 @@ &:not(:last-child) { margin-bottom: 1.6rem; - @media #{$desktop-up} { + @media #{$tablet-up} { margin-right: 1.6rem; margin-bottom: 0; } diff --git a/client/src/components/shared/Layout/LayoutTopBarCalendar/LayoutTopBarCalendar.module.scss b/client/src/components/shared/Layout/LayoutTopBarCalendar/LayoutTopBarCalendar.module.scss index 4b5e453897..b7c0321feb 100644 --- a/client/src/components/shared/Layout/LayoutTopBarCalendar/LayoutTopBarCalendar.module.scss +++ b/client/src/components/shared/Layout/LayoutTopBarCalendar/LayoutTopBarCalendar.module.scss @@ -19,8 +19,12 @@ .desktopCalendarWrapper { position: absolute; - width: 105.6rem; + max-width: 105.6rem; z-index: $z-index-7; + + @media #{$tablet-only} { + width: calc(100% - 4.8rem); + } } .overflow { diff --git a/client/src/components/shared/Layout/LayoutTopBarCalendar/LayoutTopBarCalendar.tsx b/client/src/components/shared/Layout/LayoutTopBarCalendar/LayoutTopBarCalendar.tsx index be3f5678db..1c139e6c75 100644 --- a/client/src/components/shared/Layout/LayoutTopBarCalendar/LayoutTopBarCalendar.tsx +++ b/client/src/components/shared/Layout/LayoutTopBarCalendar/LayoutTopBarCalendar.tsx @@ -16,7 +16,7 @@ import styles from './LayoutTopBarCalendar.module.scss'; const LayoutTopBarCalendar = (): ReactNode => { const { t } = useTranslation('translation', { keyPrefix: 'layout.topBar' }); - const { isDesktop, isLargeDesktop, isMobile } = useMediaQuery(); + const { isMobile } = useMediaQuery(); const { data: isDecisionWindowOpen } = useIsDecisionWindowOpen(); const { data: currentEpoch } = useCurrentEpoch(); @@ -45,7 +45,7 @@ const LayoutTopBarCalendar = (): ReactNode => {
{createPortal( - {(isLargeDesktop || isDesktop) && isCalendarOpen && ( + {!isMobile && isCalendarOpen && ( <> { )} setIsCalendarOpen(false)} > From fa3024867cef6e32843a75873dde28bcfac06cef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Miko=C5=82ajczyk?= Date: Mon, 30 Sep 2024 13:32:23 +0200 Subject: [PATCH 159/241] oct-1976: epoch live results fix --- .../EpochResults/EpochResults.module.scss | 4 ++-- .../EpochResultsBar.module.scss | 5 ++++- .../EpochResultsBar/EpochResultsBar.tsx | 9 ++++---- .../HomeGridEpochResults.tsx | 22 +++++++++++++------ 4 files changed, 26 insertions(+), 14 deletions(-) diff --git a/client/src/components/Home/HomeGridEpochResults/EpochResults/EpochResults.module.scss b/client/src/components/Home/HomeGridEpochResults/EpochResults/EpochResults.module.scss index 9a093cbacf..81c019bed0 100644 --- a/client/src/components/Home/HomeGridEpochResults/EpochResults/EpochResults.module.scss +++ b/client/src/components/Home/HomeGridEpochResults/EpochResults/EpochResults.module.scss @@ -14,9 +14,9 @@ flex: 1; display: flex; padding: 4.8rem 1.6rem 0; - justify-content: space-between; - overflow: auto; margin-top: -3.2rem; + overflow-y: hidden; + overflow-x: auto; &.isLoading { margin: 0; diff --git a/client/src/components/Home/HomeGridEpochResults/EpochResultsBar/EpochResultsBar.module.scss b/client/src/components/Home/HomeGridEpochResults/EpochResultsBar/EpochResultsBar.module.scss index 6c4d33f1cf..26e675ac13 100644 --- a/client/src/components/Home/HomeGridEpochResults/EpochResultsBar/EpochResultsBar.module.scss +++ b/client/src/components/Home/HomeGridEpochResults/EpochResultsBar/EpochResultsBar.module.scss @@ -2,7 +2,10 @@ height: 100%; min-width: 0.8rem; position: relative; - cursor: pointer; + + &.hasValue { + cursor: pointer; + } &:not(:first-child) { margin-left: 1.4rem; diff --git a/client/src/components/Home/HomeGridEpochResults/EpochResultsBar/EpochResultsBar.tsx b/client/src/components/Home/HomeGridEpochResults/EpochResultsBar/EpochResultsBar.tsx index 65d25d5f11..a3b06cd228 100644 --- a/client/src/components/Home/HomeGridEpochResults/EpochResultsBar/EpochResultsBar.tsx +++ b/client/src/components/Home/HomeGridEpochResults/EpochResultsBar/EpochResultsBar.tsx @@ -1,3 +1,4 @@ +import cx from 'classnames'; import { animate, AnimatePresence, motion, useInView } from 'framer-motion'; import React, { FC, useEffect, useRef, useState } from 'react'; @@ -49,11 +50,11 @@ const EpochResultsBar: FC = ({ onClick(address)} - onMouseLeave={() => setIsProjectLogoVisible(false)} - onMouseOver={() => setIsProjectLogoVisible(true)} + onClick={() => topBarHeightPercentage && onClick(address)} + onMouseLeave={() => topBarHeightPercentage && setIsProjectLogoVisible(false)} + onMouseOver={() => topBarHeightPercentage && setIsProjectLogoVisible(true)} whileHover={{ opacity: 1 }} > diff --git a/client/src/components/Home/HomeGridEpochResults/HomeGridEpochResults.tsx b/client/src/components/Home/HomeGridEpochResults/HomeGridEpochResults.tsx index 5132e4388b..4104fd7783 100644 --- a/client/src/components/Home/HomeGridEpochResults/HomeGridEpochResults.tsx +++ b/client/src/components/Home/HomeGridEpochResults/HomeGridEpochResults.tsx @@ -9,7 +9,9 @@ import useIsProjectAdminMode from 'hooks/helpers/useIsProjectAdminMode'; import useCurrentEpoch from 'hooks/queries/useCurrentEpoch'; import useIsDecisionWindowOpen from 'hooks/queries/useIsDecisionWindowOpen'; import useIsPatronMode from 'hooks/queries/useIsPatronMode'; -import useProjectsIpfsWithRewards from 'hooks/queries/useProjectsIpfsWithRewards'; +import useProjectsIpfsWithRewards, { + ProjectIpfsWithRewards, +} from 'hooks/queries/useProjectsIpfsWithRewards'; import { arrowRight } from 'svg/misc'; import EpochResults from './EpochResults'; @@ -20,7 +22,7 @@ const HomeGridEpochResults: FC = ({ className }) => { const initalLoadingRef = useRef(true); const { data: isDecisionWindowOpen } = useIsDecisionWindowOpen(); const { data: currentEpoch } = useCurrentEpoch(); - const [epoch, setEpoch] = useState(currentEpoch! - 1); + const [epoch, setEpoch] = useState(3); const { t } = useTranslation('translation', { keyPrefix: 'components.home.homeGridEpochResults', }); @@ -29,11 +31,17 @@ const HomeGridEpochResults: FC = ({ className }) => { const isProjectAdminMode = useIsProjectAdminMode(); const { data: isPatronMode } = useIsPatronMode(); - const projects = - projectsIpfsWithRewards.map(props => ({ - epoch, - ...props, - })) || []; + const projects = projectsIpfsWithRewards.reduce( + (acc, curr) => { + if (!curr.totalValueOfAllocations) {return acc;} + acc.unshift({ + ...curr, + epoch, + }); + return acc; + }, + [] as (ProjectIpfsWithRewards & { epoch: number })[], + ); const isAnyProjectDonated = projects.some(({ donations }) => donations > 0n); From ffb6159919884a65eb88eaac533532cbab3e9a9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Zi=C3=B3=C5=82ek?= Date: Mon, 30 Sep 2024 13:33:48 +0200 Subject: [PATCH 160/241] style: framer usage added --- .../LayoutTopBar/LayoutTopBar.module.scss | 26 +++++++++++-------- .../Layout/LayoutTopBar/LayoutTopBar.tsx | 18 ++++++++----- 2 files changed, 27 insertions(+), 17 deletions(-) diff --git a/client/src/components/shared/Layout/LayoutTopBar/LayoutTopBar.module.scss b/client/src/components/shared/Layout/LayoutTopBar/LayoutTopBar.module.scss index cad596fb60..4d778533e4 100644 --- a/client/src/components/shared/Layout/LayoutTopBar/LayoutTopBar.module.scss +++ b/client/src/components/shared/Layout/LayoutTopBar/LayoutTopBar.module.scss @@ -53,23 +53,27 @@ &.isActive { color: $color-octant-dark; + } - &::before { - position: absolute; - bottom: -1.1rem; - border-radius: $border-radius-01; - left: 50%; - transform: translate(-50%, 0); - content: ''; + &:hover { + color: $color-octant-grey13; + } + + .underlineWrapper { + position: absolute; + bottom: -1.1rem; + left: 50%; + transform: translate(-50%, 0); + content: ''; + + .underline { width: 2.4rem; height: 0.2rem; background: $color-octant-green; + border-radius: $border-radius-01; + margin: auto; } } - - &:hover { - color: $color-octant-grey13; - } } } diff --git a/client/src/components/shared/Layout/LayoutTopBar/LayoutTopBar.tsx b/client/src/components/shared/Layout/LayoutTopBar/LayoutTopBar.tsx index 2b6f279e22..ab5bb6911a 100644 --- a/client/src/components/shared/Layout/LayoutTopBar/LayoutTopBar.tsx +++ b/client/src/components/shared/Layout/LayoutTopBar/LayoutTopBar.tsx @@ -1,5 +1,5 @@ import cx from 'classnames'; -import { useAnimate } from 'framer-motion'; +import { useAnimate , motion } from 'framer-motion'; import React, { FC, Fragment, useEffect, useMemo, useRef } from 'react'; import { useTranslation } from 'react-i18next'; import { useLocation, useNavigate } from 'react-router-dom'; @@ -148,13 +148,19 @@ const LayoutTopBar: FC = ({ className }) => { {isDesktop && (
- {tabs.map(tab => ( + {tabs.map(({ label, to, isActive, isDisabled }, index) => (
navigate(tab.to)} + // eslint-disable-next-line react/no-array-index-key + key={index} + className={cx(styles.link, isActive && styles.isActive)} + onClick={isDisabled && to ? () => {} : () => navigate(to)} > - {tab.label} + {label} + {isActive ? ( +
+ +
+ ) : null}
))}
From 4e56d23e29caf1077a5c94ce96c5019d491af811 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Miko=C5=82ajczyk?= Date: Mon, 30 Sep 2024 14:01:25 +0200 Subject: [PATCH 161/241] oct-2002: allocation navigation always visible --- .../src/components/Allocation/Allocation.tsx | 37 ++++++++----------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/client/src/components/Allocation/Allocation.tsx b/client/src/components/Allocation/Allocation.tsx index 84ff425c7d..9f19ccb6e0 100644 --- a/client/src/components/Allocation/Allocation.tsx +++ b/client/src/components/Allocation/Allocation.tsx @@ -477,13 +477,6 @@ const Allocation = (): ReactElement => { (allocationValues !== undefined && !isEmpty(allocations)) || (!!userAllocations?.hasUserAlreadyDoneAllocation && userAllocations.elements.length > 0); const hasUserIndividualReward = !!individualReward && individualReward !== 0n; - const areButtonsDisabled = - isLoading || - !isConnected || - !isDecisionWindowOpen || - (!areAllocationsAvailableOrAlreadyDone && rewardsForProjects !== 0n) || - !individualReward || - isWaitingForFirstMultisigSignature; const allocationsWithRewards = getAllocationsWithRewards({ allocationValues, @@ -494,11 +487,14 @@ const Allocation = (): ReactElement => { const isEpoch1 = currentEpoch === 1; - const showAllocationBottomNavigation = - !isEpoch1 && - (areAllocationsAvailableOrAlreadyDone || rewardsForProjects === 0n) && - hasUserIndividualReward && - isDecisionWindowOpen; + const areButtonsDisabled = + isEpoch1 || + isLoading || + !isConnected || + !isDecisionWindowOpen || + (!areAllocationsAvailableOrAlreadyDone && rewardsForProjects !== 0n) || + !hasUserIndividualReward || + isWaitingForFirstMultisigSignature; useEffect(() => { if (!walletAddress || !isContract || isWaitingForFirstMultisigSignature) { @@ -540,10 +536,7 @@ const Allocation = (): ReactElement => {
{t('allocateRewards')}
{!isEpoch1 && ( { onAllocate={() => onAllocate(true)} /> - {showAllocationBottomNavigation && - (isDesktop ? boxesWrapperRef.current : navRef.current) && + {(isDesktop ? boxesWrapperRef.current : navRef.current) && createPortal( { isWaitingForFirstMultisigSignature } isLoading={ - allocateEvent.isLoading || - isWaitingForAllMultisigSignatures || - isWaitingForFirstMultisigSignature || - isLoading + !areButtonsDisabled && + (allocateEvent.isLoading || + isWaitingForAllMultisigSignatures || + isWaitingForFirstMultisigSignature || + isLoading) } isWaitingForAllMultisigSignatures={ isWaitingForAllMultisigSignatures || isWaitingForFirstMultisigSignature From 9c06ddad57024d8df0a045928e60bd0a710e46ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Miko=C5=82ajczyk?= Date: Mon, 30 Sep 2024 15:44:35 +0200 Subject: [PATCH 162/241] oct-2000: top bar buttons update --- .../shared/Layout/LayoutTopBar/LayoutTopBar.module.scss | 8 ++++---- client/src/styles/utils/_colors.scss | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/client/src/components/shared/Layout/LayoutTopBar/LayoutTopBar.module.scss b/client/src/components/shared/Layout/LayoutTopBar/LayoutTopBar.module.scss index bde3c68e82..5a58ba40c9 100644 --- a/client/src/components/shared/Layout/LayoutTopBar/LayoutTopBar.module.scss +++ b/client/src/components/shared/Layout/LayoutTopBar/LayoutTopBar.module.scss @@ -129,7 +129,7 @@ position: relative; &:hover { .allocateButtonIcon path { - stroke: $color-octant-grey5; + stroke: $color-octant-grey13; } } @@ -143,7 +143,7 @@ margin-left: 0.3rem; path { - stroke: $color-octant-grey2; + stroke: $color-octant-dark; } } @@ -167,7 +167,7 @@ .settingsButton { &:hover { .settingsButtonIcon path { - stroke: $color-octant-grey5; + stroke: $color-octant-grey13; } } @@ -179,7 +179,7 @@ .settingsButtonIcon { path { - stroke: $color-octant-grey2; + stroke: $color-octant-dark; } } } diff --git a/client/src/styles/utils/_colors.scss b/client/src/styles/utils/_colors.scss index ee77d59f10..1503daab83 100644 --- a/client/src/styles/utils/_colors.scss +++ b/client/src/styles/utils/_colors.scss @@ -17,6 +17,7 @@ $color-octant-grey9: #d7d7d7; // TODO OCT-1001 Remove this color. It will become $color-octant-grey10: #323232; $color-octant-grey11: #777f78; $color-octant-grey12: #d8d8d8; +$color-octant-grey13: #6b6b6b; $color-octant-green: #2d9b87; $color-octant-green-15: rgba(45, 155, 135, 0.15); // used in the metrics chart, not documented $color-octant-green-50: rgba(45, 155, 135, 0.5); // used in the metrics chart, not documented From 7fe4e6a2d12520b82a4c6308b1c2aad645758218 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Miko=C5=82ajczyk?= Date: Mon, 30 Sep 2024 16:31:36 +0200 Subject: [PATCH 163/241] oct-2001: transactions empty state --- client/public/images/swept.webp | Bin 0 -> 63060 bytes .../HomeGridEpochResults.tsx | 4 +++- .../TransactionsList.module.scss | 16 +++++++++------- .../TransactionsList/TransactionsList.tsx | 16 ++++++++++------ .../shared/Layout/LayoutTopBar/LayoutTopBar.tsx | 2 +- client/src/locales/en/translation.json | 2 +- 6 files changed, 24 insertions(+), 16 deletions(-) create mode 100644 client/public/images/swept.webp diff --git a/client/public/images/swept.webp b/client/public/images/swept.webp new file mode 100644 index 0000000000000000000000000000000000000000..3384063874c48b1c559813e21ae3dbfea00d0a38 GIT binary patch literal 63060 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 JoYiql765`Q$tM5+ literal 0 HcmV?d00001 diff --git a/client/src/components/Home/HomeGridEpochResults/HomeGridEpochResults.tsx b/client/src/components/Home/HomeGridEpochResults/HomeGridEpochResults.tsx index 4104fd7783..5c5044902b 100644 --- a/client/src/components/Home/HomeGridEpochResults/HomeGridEpochResults.tsx +++ b/client/src/components/Home/HomeGridEpochResults/HomeGridEpochResults.tsx @@ -33,7 +33,9 @@ const HomeGridEpochResults: FC = ({ className }) => { const projects = projectsIpfsWithRewards.reduce( (acc, curr) => { - if (!curr.totalValueOfAllocations) {return acc;} + if (!curr.totalValueOfAllocations) { + return acc; + } acc.unshift({ ...curr, epoch, diff --git a/client/src/components/Home/HomeGridTransactions/TransactionsList/TransactionsList.module.scss b/client/src/components/Home/HomeGridTransactions/TransactionsList/TransactionsList.module.scss index bb61975271..39b8b3ad80 100644 --- a/client/src/components/Home/HomeGridTransactions/TransactionsList/TransactionsList.module.scss +++ b/client/src/components/Home/HomeGridTransactions/TransactionsList/TransactionsList.module.scss @@ -1,10 +1,12 @@ -.emptyHistoryInfo { - height: 7.2rem; - padding: 0 $historyHorizontalPadding; - display: flex; - align-items: center; +.image { + width: 16rem; + margin: 1.6rem 0 3rem; +} + +.info { color: $color-octant-grey5; font-size: $font-size-14; - font-weight: $font-weight-semibold; - width: 100%; + font-weight: $font-weight-bold; + text-align: center; + line-height: 2rem; } diff --git a/client/src/components/Home/HomeGridTransactions/TransactionsList/TransactionsList.tsx b/client/src/components/Home/HomeGridTransactions/TransactionsList/TransactionsList.tsx index bab04d61ed..7971e0ff4f 100644 --- a/client/src/components/Home/HomeGridTransactions/TransactionsList/TransactionsList.tsx +++ b/client/src/components/Home/HomeGridTransactions/TransactionsList/TransactionsList.tsx @@ -1,18 +1,22 @@ import React, { FC, Fragment } from 'react'; -import { useTranslation } from 'react-i18next'; +import { Trans } from 'react-i18next'; import TransactionsListItem from 'components/Home/HomeGridTransactions/TransactionsListItem'; +import Img from 'components/ui/Img'; import styles from './TransactionsList.module.scss'; import TransactionsListProps from './types'; const TransactionsList: FC = ({ history }) => { - const { t } = useTranslation('translation', { - keyPrefix: 'components.home.homeGridTransactions.transactionsList', - }); - if (!history?.length) { - return
{t('emptyHistory')}
; + return ( +
+ swept +
+ +
+
+ ); } return ( diff --git a/client/src/components/shared/Layout/LayoutTopBar/LayoutTopBar.tsx b/client/src/components/shared/Layout/LayoutTopBar/LayoutTopBar.tsx index 7bab5cae40..f282dbdbc6 100644 --- a/client/src/components/shared/Layout/LayoutTopBar/LayoutTopBar.tsx +++ b/client/src/components/shared/Layout/LayoutTopBar/LayoutTopBar.tsx @@ -1,5 +1,5 @@ import cx from 'classnames'; -import { useAnimate , motion } from 'framer-motion'; +import { useAnimate, motion } from 'framer-motion'; import React, { FC, Fragment, useEffect, useMemo, useRef } from 'react'; import { useTranslation } from 'react-i18next'; import { useLocation, useNavigate } from 'react-router-dom'; diff --git a/client/src/locales/en/translation.json b/client/src/locales/en/translation.json index eec5bb4fa3..7ec1f616b5 100644 --- a/client/src/locales/en/translation.json +++ b/client/src/locales/en/translation.json @@ -182,7 +182,7 @@ } }, "transactionsList": { - "emptyHistory": "You haven’t made any transactions yet" + "empty": "No transaction history yet.
Lock some GLM to get started" }, "transactionsListItem": { "allocatedRewards": "Allocated rewards", From 7dd673283492144e8b89ea706c3588adc352d4b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Miko=C5=82ajczyk?= Date: Mon, 30 Sep 2024 16:37:24 +0200 Subject: [PATCH 164/241] oct-1977: change order of donastion and personal allocation tiles --- client/src/components/Home/HomeGrid/HomeGrid.tsx | 2 +- .../Home/HomeGridEpochResults/HomeGridEpochResults.tsx | 4 +++- .../components/shared/Layout/LayoutTopBar/LayoutTopBar.tsx | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/client/src/components/Home/HomeGrid/HomeGrid.tsx b/client/src/components/Home/HomeGrid/HomeGrid.tsx index 59fab84de1..4cae2de059 100644 --- a/client/src/components/Home/HomeGrid/HomeGrid.tsx +++ b/client/src/components/Home/HomeGrid/HomeGrid.tsx @@ -23,8 +23,8 @@ const HomeGrid = (): ReactNode => { return ( {!isProjectAdminMode && } - {!isPatronMode && } {!isProjectAdminMode && !isPatronMode && } + {!isPatronMode && } {!isProjectAdminMode && } {!isProjectAdminMode && !isPatronMode && isLargeDesktop && } diff --git a/client/src/components/Home/HomeGridEpochResults/HomeGridEpochResults.tsx b/client/src/components/Home/HomeGridEpochResults/HomeGridEpochResults.tsx index 4104fd7783..5c5044902b 100644 --- a/client/src/components/Home/HomeGridEpochResults/HomeGridEpochResults.tsx +++ b/client/src/components/Home/HomeGridEpochResults/HomeGridEpochResults.tsx @@ -33,7 +33,9 @@ const HomeGridEpochResults: FC = ({ className }) => { const projects = projectsIpfsWithRewards.reduce( (acc, curr) => { - if (!curr.totalValueOfAllocations) {return acc;} + if (!curr.totalValueOfAllocations) { + return acc; + } acc.unshift({ ...curr, epoch, diff --git a/client/src/components/shared/Layout/LayoutTopBar/LayoutTopBar.tsx b/client/src/components/shared/Layout/LayoutTopBar/LayoutTopBar.tsx index 7bab5cae40..f282dbdbc6 100644 --- a/client/src/components/shared/Layout/LayoutTopBar/LayoutTopBar.tsx +++ b/client/src/components/shared/Layout/LayoutTopBar/LayoutTopBar.tsx @@ -1,5 +1,5 @@ import cx from 'classnames'; -import { useAnimate , motion } from 'framer-motion'; +import { useAnimate, motion } from 'framer-motion'; import React, { FC, Fragment, useEffect, useMemo, useRef } from 'react'; import { useTranslation } from 'react-i18next'; import { useLocation, useNavigate } from 'react-router-dom'; From 6c440cf91a7eed3fb3e45eb76f406211a5491773 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Garbaci=C5=84ski?= <57113816+kgarbacinski@users.noreply.github.com> Date: Tue, 1 Oct 2024 10:29:09 +0300 Subject: [PATCH 165/241] OCT-1939: Fix improper way of handling an antisybil_status field (#452) ## Description ## Definition of Done 1. [ ] If required, the desciption of your change is added to the [QA changelog](https://www.notion.so/octantapp/Changelog-for-the-QA-d96fa3b411cf488bb1d8d9a598d88281) 2. [ ] Acceptance criteria are met. 3. [ ] PR is manually tested before the merge by developer(s). - [ ] Happy path is manually checked. 4. [ ] PR is manually tested by QA when their assistance is required (1). - [ ] Octant Areas & Test Cases are checked for impact and updated if required (2). 5. [ ] Unit tests are added unless there is a reason to omit them. 6. [ ] Automated tests are added when required. 7. [ ] The code is merged. 8. [ ] Tech documentation is added / updated, reviewed and approved (including mandatory approval by a code owner, should such exist for changed files). - [ ] BE: Swagger documentation is updated. 9. [ ] When required by QA: - [ ] Deployed to the relevant environment. - [ ] Passed system tests. --- (1) Developer(s) in coordination with QA decide whether it's required. For small tickets introducing small changes QA assistance is most probably not required. (2) [Octant Areas & Test Cases](https://docs.google.com/spreadsheets/d/1cRe6dxuKJV3a4ZskAwWEPvrFkQm6rEfyUCYwLTYw_Cc). --- backend/app/modules/uq/service/preliminary.py | 2 +- backend/tests/modules/uq/conftest.py | 5 ++++- backend/tests/modules/uq/test_preliminary_uq.py | 5 ++++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/backend/app/modules/uq/service/preliminary.py b/backend/app/modules/uq/service/preliminary.py index f6b562fd36..9c0ce1b374 100644 --- a/backend/app/modules/uq/service/preliminary.py +++ b/backend/app/modules/uq/service/preliminary.py @@ -59,4 +59,4 @@ def _get_gp_score(self, context: Context, address: str) -> float: antisybil_status = self.antisybil.get_antisybil_status(context, address) if antisybil_status is None: return 0.0 - return antisybil_status[0] + return antisybil_status.score diff --git a/backend/tests/modules/uq/conftest.py b/backend/tests/modules/uq/conftest.py index 1a46984a88..668c85a7ee 100644 --- a/backend/tests/modules/uq/conftest.py +++ b/backend/tests/modules/uq/conftest.py @@ -4,13 +4,16 @@ import pytest from app.modules.uq.service.preliminary import PreliminaryUQ +from app.modules.user.antisybil.dto import AntisybilStatusDTO from tests.helpers.constants import UQ_THRESHOLD_MAINNET @pytest.fixture def mock_antisybil(): mock = Mock() - mock.get_antisybil_status.return_value = (10.0, datetime.now()) + mock.get_antisybil_status.return_value = AntisybilStatusDTO( + score=10.0, expires_at=datetime.now(), is_on_timeout_list=False + ) return mock diff --git a/backend/tests/modules/uq/test_preliminary_uq.py b/backend/tests/modules/uq/test_preliminary_uq.py index 891c4c6bb6..a628c68f47 100644 --- a/backend/tests/modules/uq/test_preliminary_uq.py +++ b/backend/tests/modules/uq/test_preliminary_uq.py @@ -7,6 +7,7 @@ from app.extensions import db from app.infrastructure import database from app.modules.uq import core +from app.modules.user.antisybil.dto import AntisybilStatusDTO from tests.helpers.allocations import mock_request from tests.helpers.constants import USER1_ADDRESS, USER2_ADDRESS, LOW_UQ_SCORE from tests.helpers.context import get_context @@ -18,7 +19,9 @@ def before(app): def test_calculate_uq_above_threshold(context, mock_antisybil, service): - mock_antisybil.get_antisybil_status.return_value = (20.0, datetime.now()) + mock_antisybil.get_antisybil_status.return_value = AntisybilStatusDTO( + score=20.0, expires_at=datetime.now(), is_on_timeout_list=False + ) result = service.calculate(context, USER1_ADDRESS) assert result == 1.0 From 015aab2529b941e5365fbe1426ba771d50c33ca8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Miko=C5=82ajczyk?= Date: Tue, 1 Oct 2024 10:34:18 +0200 Subject: [PATCH 166/241] oct-1983: large text - inter tight --- .../inter-tight-v7-latin-100.woff2 | Bin 0 -> 22080 bytes .../inter-tight-v7-latin-100italic.woff2 | Bin 0 -> 23280 bytes .../inter-tight-v7-latin-200.woff2 | Bin 0 -> 22516 bytes .../inter-tight-v7-latin-200italic.woff2 | Bin 0 -> 23772 bytes .../inter-tight-v7-latin-300.woff2 | Bin 0 -> 22608 bytes .../inter-tight-v7-latin-300italic.woff2 | Bin 0 -> 23796 bytes .../inter-tight-v7-latin-500.woff2 | Bin 0 -> 22640 bytes .../inter-tight-v7-latin-500italic.woff2 | Bin 0 -> 24016 bytes .../inter-tight-v7-latin-600.woff2 | Bin 0 -> 22732 bytes .../inter-tight-v7-latin-600italic.woff2 | Bin 0 -> 24152 bytes .../inter-tight-v7-latin-700.woff2 | Bin 0 -> 22768 bytes .../inter-tight-v7-latin-700italic.woff2 | Bin 0 -> 23940 bytes .../inter-tight-v7-latin-800.woff2 | Bin 0 -> 22832 bytes .../inter-tight-v7-latin-800italic.woff2 | Bin 0 -> 23996 bytes .../inter-tight-v7-latin-900.woff2 | Bin 0 -> 22340 bytes .../inter-tight-v7-latin-900italic.woff2 | Bin 0 -> 23488 bytes .../inter-tight-v7-latin-italic.woff2 | Bin 0 -> 23576 bytes .../inter-tight-v7-latin-regular.woff2 | Bin 0 -> 22072 bytes .../Allocation/Allocation.module.scss | 1 + .../HomeGridUQScoreAddresses.module.scss | 1 + .../components/Settings/Settings.module.scss | 1 + .../shared/ViewTitle/ViewTitle.module.scss | 1 + .../ui/DoubleValue/DoubleValue.module.scss | 1 + client/src/styles/fonts.css | 145 ++++++++++++++++++ client/src/styles/index.scss | 4 +- 25 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 client/public/fonts/inter-tight/inter-tight-v7-latin-100.woff2 create mode 100644 client/public/fonts/inter-tight/inter-tight-v7-latin-100italic.woff2 create mode 100644 client/public/fonts/inter-tight/inter-tight-v7-latin-200.woff2 create mode 100644 client/public/fonts/inter-tight/inter-tight-v7-latin-200italic.woff2 create mode 100644 client/public/fonts/inter-tight/inter-tight-v7-latin-300.woff2 create mode 100644 client/public/fonts/inter-tight/inter-tight-v7-latin-300italic.woff2 create mode 100644 client/public/fonts/inter-tight/inter-tight-v7-latin-500.woff2 create mode 100644 client/public/fonts/inter-tight/inter-tight-v7-latin-500italic.woff2 create mode 100644 client/public/fonts/inter-tight/inter-tight-v7-latin-600.woff2 create mode 100644 client/public/fonts/inter-tight/inter-tight-v7-latin-600italic.woff2 create mode 100644 client/public/fonts/inter-tight/inter-tight-v7-latin-700.woff2 create mode 100644 client/public/fonts/inter-tight/inter-tight-v7-latin-700italic.woff2 create mode 100644 client/public/fonts/inter-tight/inter-tight-v7-latin-800.woff2 create mode 100644 client/public/fonts/inter-tight/inter-tight-v7-latin-800italic.woff2 create mode 100644 client/public/fonts/inter-tight/inter-tight-v7-latin-900.woff2 create mode 100644 client/public/fonts/inter-tight/inter-tight-v7-latin-900italic.woff2 create mode 100644 client/public/fonts/inter-tight/inter-tight-v7-latin-italic.woff2 create mode 100644 client/public/fonts/inter-tight/inter-tight-v7-latin-regular.woff2 diff --git a/client/public/fonts/inter-tight/inter-tight-v7-latin-100.woff2 b/client/public/fonts/inter-tight/inter-tight-v7-latin-100.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..bfaf238b85a554ce005f6bc9e48397f96049a928 GIT binary patch literal 22080 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 literal 0 HcmV?d00001 diff --git a/client/public/fonts/inter-tight/inter-tight-v7-latin-100italic.woff2 b/client/public/fonts/inter-tight/inter-tight-v7-latin-100italic.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..744464311e8a1919147b0987ea73195d36c97529 GIT binary patch literal 23280 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 literal 0 HcmV?d00001 diff --git a/client/public/fonts/inter-tight/inter-tight-v7-latin-200.woff2 b/client/public/fonts/inter-tight/inter-tight-v7-latin-200.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..37319138998bac24a8fb99bdf68bb99e7dd7176d GIT binary patch literal 22516 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

;|Duv^ng4I zldQu43z=es8(LQp-gjuB>cyw1%SMWrEQE~o$wrcQ10@6sC$;(YbFwGxr=~@~1?psC z!i0;ApcxasrcB%s6Kco=1QWhsA|x>ZnNu&Oy_nWf+DB*~p}m;aQCdf7AEBg84dKrXV6!gG4GGH4B0q1VMlv3FHz81X2+|2+%!&Kp>YO?ga=EB2qO-6u{*KA;>`x z1n7}KE`dNG6#;|*-4h5nPIq%Du}cSS+vpeX;lWMgl|YU9UZ?9tkM^K`*oj_U6w^oS wNf|$^HY%LZIRULEC{&#Xt!!64ZRUT`zT46?o<;jkM7vi*7oj%%b6FnO01K^p%K!iX literal 0 HcmV?d00001 diff --git a/client/public/fonts/inter-tight/inter-tight-v7-latin-200italic.woff2 b/client/public/fonts/inter-tight/inter-tight-v7-latin-200italic.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..ec40e352aef3ec88f330f8c0c9b300942ec63342 GIT binary patch literal 23772 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%0Dx}Hby?8H1{hU^dnWdPI)$&=aY8GFC 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ℑ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 RAr+dUKmAwzn9l+L006ReWF-Iq literal 0 HcmV?d00001 diff --git a/client/public/fonts/inter-tight/inter-tight-v7-latin-600.woff2 b/client/public/fonts/inter-tight/inter-tight-v7-latin-600.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..86a4abfc872d1c1f44848c8570dc948c80634561 GIT binary patch literal 22732 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 literal 0 HcmV?d00001 diff --git a/client/public/fonts/inter-tight/inter-tight-v7-latin-600italic.woff2 b/client/public/fonts/inter-tight/inter-tight-v7-latin-600italic.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..2a7a02557fbd950fb37f72d11406c68e346d19a3 GIT binary patch literal 24152 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 literal 0 HcmV?d00001 diff --git a/client/public/fonts/inter-tight/inter-tight-v7-latin-700.woff2 b/client/public/fonts/inter-tight/inter-tight-v7-latin-700.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..ba0b29388112ae8913d3da7738e06569bff97f42 GIT binary patch literal 22768 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®00RI(5<@V!0{}39?*_=>R2|4^=qsEA zbO9JV0R{koB!*yc2SCeo`5s5A$gEvbdB%s?dk4ga*Ol*e23uszBlbu(jp6kAu}^LO rSiLBoSMk=lkZR3Xx+>%< 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�MheT%g2HIMVg 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 literal 0 HcmV?d00001 diff --git a/client/public/fonts/inter-tight/inter-tight-v7-latin-900italic.woff2 b/client/public/fonts/inter-tight/inter-tight-v7-latin-900italic.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..8d7cccab51361954c1b800a7ae77b2790ac2f512 GIT binary patch literal 23488 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 bJYVMjYAm}L(bq7RBzUaFWdDc%qltVD2JS?{ literal 0 HcmV?d00001 diff --git a/client/public/fonts/inter-tight/inter-tight-v7-latin-regular.woff2 b/client/public/fonts/inter-tight/inter-tight-v7-latin-regular.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..308f3c02df905fbf2744f4a6a6160465dc686149 GIT binary patch literal 22072 zcmY(pW2`V-%q4nk+qP}nwr$(CZQHhO+cuwLpEK{=JIQ>rvXiE}{nK=(yJ?rZyciPz zAi#f6;{ZVT-|`d#06=y1|G)d+`u_{8KnmL$bJuOvs1Ce1I_IUcn+H3geTRG3sJ=z+9%OPXUQp$#a{Lc} ze}93S#xVKl5Atx6I6weu5xchV6+7pzDj4IE*xKpSSam&Y#ZvUQq33PO&an z3|@nQiH3v>21vmY?dRal_eR7@hQNQjq`L&5j?_>0~5c)ZI;NwGeC>82TdqB&e-f!79Oj*x+CF z%}FqTlyS#{Zt+z>(Q^nHxd^a>e`|tET(^C-0S#PZ0{%a|JRnm^n+Bjff_bf0=bwJ% z>UC*1J#f^=t^DeNk!)oSl;Ybt`_vwl%#jjQ@ewC&5DgACw_tf!BFENL5-7lbr$R+% ztn2nn1=>CX4E)<)U+D4+@<}dlC|0wXl5 z$~6KUfsO@QklT&)_+xY-A)}6@W0B*3nN_V{jT_;hK7a`(+Jlz)^Nj6WpF$I>tfMpE zfbQnt(sXDfpa!T2I2|x-1RMb=E?^K+WyS?`Cm>u&b18Hu-=7#&I9)@go|LNUDyyia z`>qfE<+FQ6wdd)>7o4joSo1IC-`a*Tg$Qt~I*-XoOzp!IiidZHa7X>RMYM;&182rU zjsO^;vw)^n^QnH)&%g&<7(3VkBHo zKXTuH`k6hkE}E{Zs-mzMiT;C_NUiU^y^*rBo{7z2)M=#3udgd+HvEEk2@g+CY~4oWDtdIpv^Ljr7N1#p6Y}vK7qb(=r^Q5 z5Fw1h_o5Kw00e0kNeWZzp=K#v3M-b-W(8@AlWw8P1!U=-2Sqq8Z0Y)_LR|+^P(Wb` zkHOb|{ro`JXYuKP5C9bzQ1?~!*#N&g3m_T-1jK@X2RJAs2mlb70q(9cfNMh_EPtYN za^~iwj1f~ggi zWrX-fqDv9N3y;ADi6jtA$^dW2jazMcWKGuHd;PiUWvL&S z3*5UJW0~GX_zLH&~1r)+RpsRr@33wWcJh z$Um-A6vKb{B+0Z16P>4rq1Wok%3;FyY3^T>Tp@5whrR@lm05Hi~Q~Mi!B0Ha?eQMo@Q6JQ6F zws|2xHkE9f{*oSmmZ7$%NHFA>Ve|0!m!(1Isco0a=(;C=ra1u-AiuGhKvjWSdCb=fLjDA97NQ+(qU^{)@tB76!P^01n z7|v+a1Yv__tKqCzZfbrbYw{A&&+54d_2+e7Vn5Y<`Jccs7_fwbfBP9bPEK`Q_cYR~ zC1Fw9{)K~!viMSFqq_LoGu#&d(9`9$-+eu&-A)w?Q?pFq?t@AexR#fvQ*Y36{Chgp z9F^>y_NKx{v?&&|6#W5FsYxGMfxV6yS;0EiR5u^YCv{40Z`fB+OeivMT;ijqT6uV6 z80-XRZ51&QMsY`oj!;`_TiQ39rnvZ%10}JlaB{lAr%czuB=MhMRT2IcZ3rs-rCmS} zPhBHdeUXOCsaDTStJ&v*uIrej?0wNEdT#O*zHqr=9aeE0@B*r4?$1pu$PRWYM$L>I zMRhJcfu`2iKxe)~e`($-gz!4q0xsaSQuZMy5ovBV^+ISJlOqy8dp)1S=o4mwusv4P zoAVVZy%AUgiIAS4q=zAMRkJ`dq5LZyW&093o_5?t;`i(Efe+l?_~WH*ppq9m3oLGn zxhW?uo_24L|K?GJK(ms~V55mhV|!Va*feA&@+iR}pP?!TkagY}P)bm3dCh#WT3JMU z8s+J?n@hjoB=pLX|M1Z5@ZX!e?1d9QX}*dNXkFS=%&YAM!vS!6e2Ke0I@{X`={gnY zSM@8f;&#IL(*MgY%^PlAJ$5~}EqvPaDP>^Hd#^j-FR(e=`^{A(#maRbu*QuSS-%%r znULSjTuz~;dDDMojBq~gdYc*?*s(^QtPO2>gi2&>(Q^+2l>_JV3rO9` zIh4u<TgXqCdNFJ9TyvRhsJ6zTMP_6~ZSR^@|pcN0SV4HkS{XxJ51 zh(96pRjN4piJjigz|(!!hb(-LGkU-6*#8xtucXV6=dAwL+*z=1zX__ISKj6MU8>C! zI1A-hk8#;e6_D(jAz#1BLXIt6^~_F(E-jxAuhA+U%f(HQTD1<(y%jf`@}~A~(^blysdRc-^Om*0DSELo@z)s@1rvw;$D` z_rcEpjQYK#|Hjz=9Q3hO?YGVlL>Vv>L`ms`X1R?2?`y&!IxxS8fbAdFM?e}#Fgub+ z#{-f~ih;>2B!H$d$+R(<1r7JU`WMnZ$C5pRFrZ%$&@Py=1PVQhN|htf;W^TlA#awg zvFRpJfTTTL=n^v<8%e?cEaXjiPk>QGf zhQtOJ5*xR0`x=ygZE5K|8 zv&#E+aMvwA3kn$>CJa6fJNJ?AZ(|YCZcCe5#Lf%z5Pm)P*(pjHl-IuuTYrgfI zEW++M>#o(CFo)U;LypdrBr-+eBn6BNBQz$JrN+iWgMo<}D=VbjJfKrO0JJ?ZN7vRK zkAq_*K0Yx?NtVlCo~Ovv6w_pss;-yNWVez6ei7%{|=KChB76SlJOu8Ee#-cMOiv71L5{9Lr{#c@SaGk zgp?JBk%VOQtc)1rUTwIBu(Hw9@5);8EpF1iTaiUn2rFzJGskh^9DV`zYKOoy0=>#$ zVXQb_2o`QQ#E7|o&xDz(BfFEivzevh{8F9yzI*aJvk|HrP~KX@o}mPM5m z=isQ>U^19Ay0yowJw4f^A(wWFl^sS*&X%*G&`@r$pG_G7TnzlA>4d1xjSzcO1%^)V z;T+U4b2z#RO^3G!=)PpK6O;YGWH&bJfyv7}#lv)Np-$NX*W!Tu_BLmxc=x?WuY77c zDdW-3tB2#P9sNOh&5j#aCaX9fnIkCLma#K-?}1hN6VKDh-$=mcQ4w(=SXy+dWDV?r zl+^pw9&-9`_G75x{Cld|GauVGY*R}hvUP*^BVMB3!v!~aMl{ZE7H5&pF6>C;j?jAC zHwont(f7h8wY!0n4RuD?__Ox9oKV+#$*NW6H7fmLRO)j3U(U8)HL_WXl4q?#s0I8c z>j@e8y%g$YI-(=wV-t(C+1}FgKJeP61Bd~^KQwmSG`p1`Y*OlHI#dWa!VNmtGKxJ$aUd{%wfc5%AXI9oU7CUEMX$ZfvYqS&9#1-LG0!QvGR(?&oZVHlQK2cp;* zrYK?}j$|U0Y!^&s6A^bJa;K-~BPxXo%4lP#R1*t)vgoqO5E*+nSlK@j5fX4pCE|7( z`97e~D5=X=i>*zRRiD)YwcN0!4x3Y0u8x;XE<8wIFdmP`quFqJ{$&~VVVUOlY}$Y& zqd%WV@6!lr=V_#CNl`!yby2MZI$^VXRTLozLY2s`Ohn8-lrYvzs!GI*!JXn zXg;43EXNxs=I4hwqo)6^=6bGc#AIF6<>K{xKVdnH7ZMJcwl2+PNaOA@XkMr<=Usdt z4^zt&Egv@t=7Oz}*nRnE+9RBbrXmhbJfWh!IFJ3k?kp4-gR&6G{sT ziVBSlWZ4NqxkZV{2uYJErQ2|vI#Cke{6R=nGycF;cqGDeLBkKYT99rD!jk1UWvLvd zEOEnfT?a+eccQ;UkMX=tQvYN;Ze!b<^ZU&|lv?{ledr&E{io_5zhWSymTz)cvk|zL zelqhC`Bdkc)j-#Yl3l^Ut#&0GK#-WAs90HCU}R_xiIiM~wF#xFiD(n)gv)9=^0D3) zi0i-2hpg?Vwo`@R2aOH%Yw^PKMVqB^Ew);)w@KVk{zv96%jN9Z(%u9bg@3 z9dIWY#4(2Q${|~*eA&y^4k}zv4L#N)l4#8*WKTPNK%r1mm)bseJG>~(&TQM1navwl z@zLtw#0dzC3zUR~#|K8F+9Z*TQzvLAlj{Wvi~3rUF}N)*Xy<__qwrtWOn#U8f9m)z zArck|jITGce(fr_=kwrIX=VOLI5lOioTjGIG_5mE)HN+rR@ODGbnH`QJ2A9vTV*?L ze^m{`a@{v=>wKw-^}@+#H!Qkc&EUPC&)5!Q1%&6vP7b51^!|0~eXG#>)?4wPXXbkj zNsan1Yxm7!Z%YnFA=(;~WMrg^T4a2|FPjAC9lki@irw_%{yC`)-*8ie;kb?~jJI*z zigIiT$=#uWxJOPk(LUToE}cQV^VL9f*I}RUiSKL_rU90IaJIQlb!8REHIgd+__7wo z_kA4<*yamlUGe$>qBN3Qi^ zJcQCWqr!~N%BY{Zp61{>q(Z9^yHa(14c>kr#qzLPBYMqHNqoA>dPFj2g-iXqA;fbPW zYjqv;Tk_&!9)T^& zUj2V2q-J-5?()|(V?#`;Z6l7%lBVoMLX~IQ=2@gd^s<)2N4xw$C3(p$6PbxqnZzx`ctp>5 zGV>WtXp%}M)D9SJQ_0KA(f~Mu#$>z^tpJiG@!{A2$SHkbGFhcUA~OLFfVa9LyWz$z zLH*bJEfWxDPbT%0fGzf`o#8DC9sz)Y|N4=h?ti=K)9CBTuSD@HLx)(+k(k)&Fi7sX zW)05>ouK&Ajj?v_`gu5d_DgwGJ_TOXL<9Og#R!Uv{;MMjLV1~v6O9+&A@m*)xF_4AyxxV*r`=xL4A29a(v^Z0+z zHUNa*j7ABG&w!^=J`5Ls0+I46MGzTHusG{}}(kF}YGtk|U{( z8vY%B>a2RHU1n7zHl@~~j0(#7KTFSZTh*GLRaMSwsi{eVo+b!dxkd)~wg6cU9Rx7z z+$K%cXba!vFbA$WY!n2na+g%Q00{)Bu2#IkD)G{6hmL_$6Jv6z=bG;5gKKYA)kzqv z2$EylsHO{BBY<6#8CY9v%5^PHUnm`V3tHpR=$^alMu*x(PJl}f5pEt114I*k5QQ}! zF7+^w?Q!}xu+J^skTx+mF$b62K|=4!wI=v9a4#inkJ>q&egLvVrFY$|^19EzSqM~IX#;z%fClv4U!GEnr4fGOl%IA--|GrO&KSk zRoBkHibPcKkO7w0POpVm^Kf~$T3K7ShpZAl}LgDaOXO zt*EsJ%Eis)eLB-e1%5fm`~^s}0gNT}>AhWnnbIuS8IM*;E2<3)T&xyeo+@OCi`ImL z+3o;AfQbu@4-_?W%_9#X&ZWwdu$ur~zO{>wc`1AlC3uKb#1UnMuhv@FQxVvx;f=ef zT05>07ZL^*u?DA=q$Bw^ZG zEBpzLTf0#{8Lq}MjBh6u)<8L_6m!jPv0N+^il%$B&F`=U0|xv0AvD)rRShh-hINfD|9sKiN_`hj3Zm!o-~=#0nbzo>!<+wzfkC2!Vk7 zI~>UX2n);+HH_RhxdRnlz!i-+!IAj$y3$hJwoFEqGLc4+B@Mu5hhkDPu4`fVW;1%r z7@xU`nU0(iM^%+Nr9oUmY_Y5{tT4QK(l|tl3M2GUe?-uwpLu%hKs1(i$uCVA-sy z9+GJ~b?t2$;~=uEA?lYWkH~?5hGWs$00KnQ=~4;e*oSodqzkT#bdh8$Ch;JmOgvh# zU(s>|gA;Y14mEX|%Saa>4M_G9Z|d6j`)Pa2f7BHs=t$^8c7Mj}2W~&d>jJlXs_kcO z|4iFI%Jy%j1Gsj%t)I8HRaG}dRdeIjX{PQYQ+N1CvxcqvU2SiP)|w*K)s60U!`R&1 z>To`uIxUr_n~{lw#lO9^v9|O$K8;EWx{({P;cBqdos|~lbXF=84SDb6)X2PeR!T%X zGz98wOBHXYgU!+E;b&%LRLdeaR9;w2SB+NBO(vK>b7(8$d+y0Aot*zJA!l5~%pdSD zvj7|8`CN?gIw8jUZICd?dG+(ZYpruDh0eVV=iPqDyD|9MS`FUa_pP<9N~OK6J7be< zS(=bzYf3szc_e|JB+1km!i;5~+c1hGWOw3>k|2V}7Vv0>vua+eR->=h=*+5CkyS4d zI;D5#oYE>=Qnjy?R@_}x?J+up$n8Aj^?&WMr0>27a?W!fV~*oKa*X#Hq2uN;h>l(B zTDPL!xutuW6_g2SP()x$C4wop*@t%{k2w|;l!71(VI0Fa=7>Ayz>6P0exN8#p@RQD zBqYBAWt3X=Vwo=#uM zZjxHPKU}NL3kr#cMT1d<#sbjbn0{X;sgA{T7tTV}R^7JEb4;X8rBSGaA_Ag@oN2bx z=WTAUH9f~oG-Ic4%=%vq98VdEr*?C8@P?N#Mn z(ZR?UGvRwqyDj?M_nzc9t^;`Ro&#prwT)XwPp})6eAcq6swxU?@d8_lq2v1*+HAh` zz$w~x9^=@04^7g3N1*08E;EMlTh`^aZ5s#JeKw(_Xc@5xYb$|lt#w&?RTVjBDN8Lx zP?PlSI89R|xh!2Zc#@<@`Zh5EXdoDiV+pnkEadx@kg4kRxLVEQbSSuB`O54NkoD%kU^1* z7mqR@kOI9b@q4HgZ8-m>S#_#`-DW&ZyfIPl$_j$ zU>U!I@97WyIPlb+pNnd)#&YX9ENY(Tz#mq*J(3sEFlkRP5F-g3CFCY@q%0!%35+%a zH4{*vT(^Ogmy3=wkr-dao* zV~V^0Wj?RLk?~6QD?SVOX>(^eywjR+6V}(_#$xkDk>EGE-dfBYpn;H{bhQ>M3bN(k zWOli+6sbWDpZCMS1rPdV|4It)xiD*OuaUd5OF(5C5*SDTeIyI{nugs3U=9AA&$F#5 zEXMmxz+*6MrGTwesRxjTL7?(=-siZjwrLP)*B~r+GKA~x{>z5Dfd)W0RuPcQ(XL>u zk#c~>T(*L-E7abAn531F#=g90xmsNk%P3>5r^Qd4Bg6KzvCgLzs!!7HiRP353Sj~ixHnNF0emI{=A>FLb8#VV z>~o^D|EJSMIEBU;P>p~iEBz{D!KbloZf5AQJ(99?@i0DF%GlN zQnJ}yzwiqb32ucog*4(T*arm;+3=V4HzA_eiHXEzFdXlJ%xwd+6;cCl8iTCA0- ztyVmxrVR)tO2Z;`IVZ*RNlmVQQb~fBCPC`s-jo4;LjahEBmk&ppye_3qCTgj$utpR zmuevN1>$4M$s}~HN1U>=dt4hLQ9oQe`f^kaZ$*SYa@2mXFl6DPKgd zAxbCBGr7@VjATZB3NJ>8OAigqd3yOTb*k1E{z@c`4tJ{sBEC$`K<6G>2*KsM*?eul4 zUMrR@yp_9hIlvHI$8ER5F~0ZTRQEPBN#YEn^;K1?cVZ^AIuwC<42X=+L*)s`B)2Tn zIe>75TF^qmK+A|?l{#cN-~%dli%Z|iT^eoTK!I!ebRhVfIpnOiEk+J+E%AWqr)oj_ zqMQyJ`33Rk>Ok|K2SBg&O;fCCwWMG zfN+BaIK%=FERA;1*(3lg@fdNUEQ0J0__ZLO!~_i|)yp52iR~N*yIoA9qd!s3KxM}- zi)Ub@+9I77-4fDeBneUFgxCqmBjkq=^fbW3n(4aL=zdkuW(`MWRAZ%6POt62wrzf7 z2vP1fJvwJ!pFY6Km6*VvDChWw259BmzYg&~Mu7L%uZMU~&y(|}Qzd6a|A^Xp<~%p> z5%`V;5Q6cTx1YV~+pg{v7y>wREFcLIN6%rTppT`Z1UfDxBO)}Y;nC(G>xymM##{?q zdwGJ>R1~@7N>4UTWzBnT@0%mqcJqx7e{pk@ECxh0Vmf~r7t4u-D}zrA@5HsIC8&6N z!l6YiqsezRZGilvg+Did*k~x>0^&l$gGIs;$>p@82 zZEqYjl6gTSO#~vTl01x#3V|uIFlx_dX0*%S0?p$RPh{AljV08(FrK| zx4^*v`hXEK5+t37%VB&fUZ6yVc#18ufJ1`hQ4q}}`2z=_jR`O)GZF`Vgyc*NItUam zKx&nsLps>0W{K+Rrln$uK+>mkj`wQl7YjPi)<{j~Ft2{*hz5R$0W_U79ip7+0;ps# zVzRcW~ID_*?>>s~o}DmCSqNHqNW zw(1!XfjeS>-f|h3{>x24;G4y-2CjsU}O_5R>l(JhL z-%(hLnHfbS`TLF}bEGvC<@wO3QmG4MwGI%P=>+y2*H%dh)dkw^4&>+nL@P9mhm?v# z8Jsv&QO;)=WR$jKdP`?-Na|)pF;o%|)y+VxhGy6=nebxkBxdP+{!pX3b>X29hl#7g5p9mlFXrqV9&;l+zgqRFwCW zc%Qowb~eu2p$WKiaZM!@(n(`ad>%(z^M(>n^T;HQ?A^_ z(~b!9IO1}!N(KAtGUMmjqUQ5?Lh$=^itmAXcb!tT4&Xa`^5}WbG2s|zPl=GpDHCBF zuqYz8O_hJWuP_oiVL2>1a-^%CDL#rBeB_toh{jZbv`rXFDBO)I@2g-5Nqq2~>*ati z790p;=ruG=n56;J1w5YH#-b$XZ`gA%DqXBHiqT@g(Fu439RrT&c=c)TBh6IMHbHryWADm5fXbR4{ArPKLlSF)2&oK<d*_)$d)%0qY}Z>ZVZs%N8SO!wKl%ge!ylUapZaOc=!CnNrC;Zk=XcA=2*#gWE; zhX#br&oH65Qh=os@G!;SnMyXwhcgpW{>2Al6}Twb%pokH_Rkb8HMoQ)$B|YJjF_Tx zrZ;u#cSSDcr*sw1LU9+pn`A{Cj>F{?F+rZs0)fy=IP%NN0vvfc@C;&S<QWjF15D=I zM!bPvW(2iQIQMC!3b0M)kwURF_twv=tX{QrYT8WesFxwD3uO4ja;lBhlHrV4-3B{t zd>jsBbzEU2;yl|Ap=JX}NZ~PusoN23qcs$*T1NSxbu4NP(4ngto&Z@ z4-q4uw7jqZIggM!Z}zu>_IWNoLf=#28ia`vK1aFYCO_)uHA>Lgu@!n}M$8qfKVp-p zYK|>8)ju*t5?74Z$`i!{NV-BXcqk2Ad&)uO)8m5`AjKbTO&h37@=ktOHq8rEavGu| z;0U^aVe}R0BKFu!@(2FBSR$}|f??$xqcF+<+NZGDQBZ?m()hti904rDP)!J2T2(!b zeNo*jf_H5Y-HPdk{AX+lHd&=yA1sNzy9~1s%$+%SJj5c&$!U{@>7kY9x8rm>U#x@K z>Eg_6uB^B}n@o$w7|Sk9>w9-uQ;HotQc$huM1DLXXG+gpzDaRNYmc2Rmij+s{LuaP zjg?8*6AFF(j@3}7pHZOSN0m0U?5X_Mc}q~Wo28$-QQmBAiwJ4y0M6$D0!*4Q<1KX(@yzW^fyjbvUI8*#OBSX z3s<1Yu}oY9=CgK({}IlZ8=3B3Z|jE8B1PNWov>Q?!soa0!*}QZJ;}M;KwSTWHMboQ zy}UVKfLg&~J9R6(e!p$IKqs<=z<-OTM~(*>qV|{Udw1isw;sGcS-j23ZQO`44V>j{ zpo6k7tNhV`rj}9cf&*P)ablmX1b!A+>y^Z@M3o}=TI=0fiAU?2I&KibQ!Bx) z{O16w4zHlDXkd@K_DU!;pJx4P(?;bCi#h7oUWd;0Nsrr`qLLxlFU^K|G!`Ic)vR%Ls9f|qnV&Zq6`p{JS?|I|iByIQ(iO#*q zc+w@fIJqJ>)z1?%Z3~>RA?~%=(Oa{-Jo!SS@ZC|KK;5O?!(|6@x-=2w2Na=wRIG~8 z$=x#q0M4bc1~{Gd7|Hb{G-&@)WUUD^a#V1Bax^0V6_fas99R=Y zGO>~IX}Tyz4+jolLo%-ddq>R?n9`a$uEP)O{=fzl(eGiCp`>B<|~$m-yXxyZi%Rl_2s@4)(K)wvHGs??a(_=L*3;_+yxo|@KuRv8o zKOSN20hv5JY@7|YFdUHrupM!W@42cb`3h5xrtELx>2Z@=orQ!p4gW92ea23vcTG28 zv{-Z70(0~m^K#W93oP;U-ok9?cu(HHO~|`#doqvjCS8iYK*uaqW^!{l zz8|w3bRKU-@8p9T6ulM5z4qYImmd+he)9c{lufM3O4+ey>F z?m+8y;WajtjMrRWC#UYsuBQeX;}pl6(w|z8*}ZLzR>oh$ zaJ}3(VZ)<$?(MDiA`_2(x<4FDdWrt+Wm>L(nWkLulS}R;hFTv$GS1}72_Ph#3TkmV zK|uDXdRM@C zHc~>_e~WSUeAKyjn0E2V3~iztTX8A!Fje6zYdM-W8QLj%rFYx2>M=(p zU}&Ve8vl*1T%FckM!<1GQ@)=jmT}jJP6P1}wUAv=S!??BG z*C9A-rF=(I@1+Mv72+fkxCN4~r>7zM9zQb0eQWnSS#Y?Hwi-UV`^1^sSC%4?nVLT` zV&t>99Gljo2rAW>VZ>Xk`Sk_wIs;B$77zt8!gaGAKa}}c(RqCHUUnWaZysJJI~~kq zZ@+h@vJ6qE28}I7vf`b*`<%b3xAk`#r4;F(a?ka7{-6RF{uT!=QNQH;n=WO%?quj1 zs)!!M+llH}=}kZLQHCUCR}IU0qJ7p_LpbZ_OdX$b(`&zcF9rYj*qfXZ$q;h(!Owr=~ zR-t_k-9Oitxp1-DZYtj^Z0YTZ+A5P=-T6hAcxg*rwmFSN-Q}(tD5i(FoAfINs)dy> zzZ-F_mt@nDM>sY##qn$F54o2$9elGb)*oxSB!!>$7q+E(q}`=V{A6|rhRXd9ANXx+14pyBZsJNI@tR?_#?r21->ljhpdDpP4aPn^-D zr%Km=A{I9Os zECq5R(yUwbZ5KK7I%)L(I}1w_f`%mDxT3TLYfs#&Ox=bc!p>YyUspZ03?iX?Sh<`0rqKF`;1f3#T|N)kq?X2-9!`8}nLo^Lpe zzICrP%a3of`sd!=DQZi62&%1)gWuY+ZHCvFeexy7pUa#e*M3I1dK+4@i3I!MArAL` zC+_h(#Rl#f?(lZLmV$>QalU0QGrP*_g2A~;l7$OJnw&Iiq8?0+fci+y<%!}H$xCYq z3OF&Qk_51E&cewZIuhnYN6I?=usV?rJtEVnaOK~JD{IlTX!Q9 zcT7C9lFZqa7yZ1>pZO(rvkoP7Tk8oQ=SSXZ!Q^mCVgzapM-Rss)iIuD?M0r2{dtn2DrgBRpZ!P}*@vNX%~!SE4A0;YwtMP0@p zL~f|)`_AAw$S|xtGFKoFmQP*{4jy1gm7dm%$ z34LWvn@rVprfCX3=Q<=S0qP}knko9Ds^TPJPRo^pe!>~q*~~!S^Y;c-^@V|{&elrJ z`m@`rT1*s$lvFKz+WKN4-DUl>nQ%j3De{)5!DJ~z+=wadcnSFH`MvbB8lKA*;p1Ir zzQp7ah>FET#T@W7LBOT$cI<>x*f<<&fz?7#_whuugQ`=#n?G95nf8+c)0vNh`HCvQ z3RREk7!@PTh!>Xon6pTz)^b$_6X$sf%>x^q=f%E}^!)N3p4yF~ZMOhlnVT)Alhzr0 zhuZ<=+;RFwA3ZLf%q-du*nYP49mwBzwiY`WmdY`b5(}tGNZhRm3DL$_!WF}c5h(kJ zq}<0ZdUY|Jc(VeC*9pX=pcU1zXunL zosaf(T-mcfxaz9bzoMgoaw|l>G(`(}xdRPoZz!{BP5-Z?q3ZKzXo(Y+x~~7Hs5OsH zyj5MWOn&@n=Bl_ZOWBU3uE-N!eJ$418uQW;^(&IpOC(=0TZYznfkE_h#HGs@UcAv^ ztR=eabE3CqYV106Dp|^UHSq{w#cOb&R84qwR6SH6>H^MtnBqOB>6aBcHTr^xWaL)) zfDLGlXS)_>nQ{H-l5}%;)wBprZc9&-th=6CSjuAeTQV8`uaY~d^ZNOS+q!)j>rM?z z-=#$>pTAY8D*hi!o4G>Acd?Xj&BYW?AvHbo{4jyP7rp{>GH3?aWkzefhSX zcFp`2c5Hp#`U{6BE|6Tm$jp`HX|%|$QC;x7{JlMT@URID2&k%V@?opt!R_*smMO1w zfS1750e*$v^Vj3{2rm}$4F$x0`GV3!9f%QaT`s4 zAF1YNi49RjL}wJcLT+rniZ!jd;K#=Z(Qu{74&3y18sOo}iG$}Y%#e26;7=czIdN_a z4St$T&7xHlYfkq}6fsq%4GHqdEjIqn$QOn2_ zxT=bJncN=aW3+G8bit(nB!r>eHrCC36uFEHa;&L~Eq4pa;G16qAs?_kp{kuR&wX_s&xe0-{FgkBtO#Qyx@<=;VGXm?HzV$fF<|BwW^?otKrC z7#gLwFeV1;kjB9_NVT9_&GN*scQYSaO3U8s^~2uiQcM z9&y$=FTGm#m_9fja{JR|4mOgmma3c|?KCvg!fIkOwJ`wcr7!cGpY;7}j(Iz*C4LWS zRyqBstw+V_QSM$Td~dwwH&(JGj=>zIrVmE;PJ?V-vkz9k(P>(%-!+2iri5y=rZ4Vf zM3r5WJLwTUWD-V0cb(IvYbpJ}0dp7Bx>RGS=LK_2unx8rN&jZd1rqq%$ z%}h#eH#3EzV04%t89mw48dW2mTP4w(5Z}zBYzP7b7D5& zB`UH~eMpV}tNl@K{nlq+Kk^J*rp#Es27Qt{?-8&U7=VA;X35-Jw%QL4HHa{Il^LU+ zw}81KM7sC5#%5mo=ecQW?_2vV;)lf_ssEIoME_Sm%mBT&C?1XD<3|r6h(e76lvtr| z5W3NJeG1K8#PJ_N8a;@LBT-RtN8+Np*lai!fXB`*{k~PSk3hp0C(UL%EVICtnkkaT zHNQf9jL_uA+eL9PoL!v;Okm8!Q)a*y+(EugLHLS9W}yRyN#=1TD<@ZtO`l*mK1)7r%2ZxFdqyWO$(@yB;cms*CAV&qc-*hf3Y53=F#`o4el=H+>{Yts~98 zJR=xz|8VlK!!XNV8Z`Wm?Hksm8Tz?q4$B5gVIV?|bYW3&MqcS1S|u3TrKS!GhV;r5 z4lc61NdTzPyrlNMl7dC}H4tWvq@jpH(G^3pjjtprGU}EdT+fm;&co8ZFPywTI z8~pn|RjZu3`C&;>iz$)0e+t%Tsn7kB$mGqNX`!JBlKW!?rrA*e85=G$1&FORcUeg# z|8@TUgK5OJ|Bzq+=YGwlPhnsVeEh*ft4L#eCO(`P5mLD7H zD4I7y6_E+4n{HSc4J6Kx?KfQ}Q_Kpgs%(HPt8_v?jnSBF8~j&~+JNkNh`zsf7z+og zy7wAf*x2B(EEvfw^~pZRWN65FVbRC)R!3SsAvW+F%cutQH*cg@BW`4 zg6|Kc74MYzn-mj9lP?$p}2)noQihZReLQ>$aHwX9d@%V`tFeEorqKCDa|H z$X?*hfd^T*)8T-Km%_b%u4hVv*$XjcaA4#@LJo}DJ3sUbyKU}+g((auhSqHs@BZHy zJU*E$7K3v6AeK63E+l0v=59CNxMb$`*bF=dlR=K}EI!a!3w9V5N1-7oR#jaB_Lk5{zSd9KT?Jz z0vSYvh~2r#EI_N!B#QE*<$2@iMG3BqlK*G&K0Dj}Aaq%HeF9y*z^^-uk3&NW$T)xnVs<(OifMt2wsl*a zuErYw&#=ze&G9sIGNpv3JGLLPmw6kuiY3uqxuBRJQe5$G`@sX zKJ92MBY>2PPLy#;yXo6PiP*TU7*YtR*W|6Xt(M=k&@2#69Nm2ixA^1c5OeUqmJkcL z)@If*e^VB8pC#-MJuYDfP7{WAV7jwonSX#!d|?%a+OwbFo#z4%c)>>qL*M(?D`Nf# zo`%YeXEuVEvgGd5kA8i+C{wMsZJ1{JJ&7Mbefwuk*ivV!1OK7jJl_;a0d+Szc<)xY zLGV_Qg{3KQ$vX%W7#{GW|A)*rhuHI89^H=7hwp5$j6=unae8>zd)RE#up!BD`|$^E zk9C6${-<)qN}}VSW6_877_au?IJ5_|YYB7e&KAq**jTFvbDw?eVc0^Bw!XHVeqFTA z%gKXji${Ag+Z^Iz(04Y=;?Oo(EZT!+?`^Zip&K0B#O8=gNoW1nD{)LYfIY;FP@|uu zjC6@1qxJ=u6c7L`Y8ImjS-w-*d6f{Azi6mv*m>6V>L{o03(!`vTWmzvQaU6b^gw%- zfstx^Y5B3WF~W|HZNkkJt(JOHzSKYP)1wx$>Kl|$1AL2w2(+k}tMf+$HS&!}xBDIF z+>L{Lh$L0Mo??rW7$!!AZx}WJ<5=s+qLTXA^t#r7u}mwZQu@c0RxJ)y4agIX*N~Oc zoUl{JvYadb>nva7I|hx+z~qnJzDtm51H+fIVgwTpQNE25a!7|j(_*anj7p1Vx;?9b z>=#Qo)O|OSs7@MU2Ewi%POPy`Ee! z@Vq{&)T;A$hcWjfcQ(X-F)Y-$ffZeg^nQIdRyFxSwtPf)CM#Kz3GI^B?0lY+PzrP& z?%@}PL73B$T_S8Hw|q=zuFNzxG1@K0icP#ggHjwpM8<|-6c~h?FZ&0PN*FSck%*eAR=_!3?;e)d z*~UVKhZcY}vhKC$ZUUy)ERSkHt2KV1FKt5#%WTX1qsd|3TlDa7Z2spBh4o+BTLCR; z2Ug`z*3>xlIvUig#ZHB3nA#UQb|Ta7EF5MhDn=U!UWg09PUUSVWLN?@F09dm=k*2o zmqX|L%VA`4y%p|r9gd+;Yv*X!mydE3G3BF6kLFMImwM$5mTLv|{+IDM@WVJDk4wWN z>m=lfZVUV^oJ5M&i!9~!geHHC`LA+cvMDzapPwPK>*+h+)vfH^NsRny%!8z-4m6KH zY3`JO>ss3vSnX--99|GegKwYW|7EgmC3L;*YL{fh7+2|7kGE(oCKH(9tf>#09%dx( z2Oj@#Lq=1A3xaZ8Oob>iEDBN>!5AjW^zubW5&ZLOM%JSQve!zR{0+Ml1W-_6;T*LA zPO+1N7S2ToYUcak>q;)oHE7D9%?Vma&_aV!ZRQFSE<%bl=`Ew^mKn&BQ@LE_;V{3S zF7Wx^w9ozN73Ti@M31rm-?e(`xwy`k_$&4A&T+Iks#YN3NVp8gPXX$jSKvH&nw_5=43Ls#ZE;v4b7#U~}{q0LG6r6JXD` zQ@#77`%X*GwH*3iR4o_tw|xHkXYJdk{&xSK|HtNHV=s1c*zjk&<`{Fq&f$ElpPf>j zpYL~DqyY#(6CGagw>hz}PiGeN_HU=hpe35#*#La!!*$iiK(NAZqM6#pc=lUOr0-kB z-a0Arf~F0p0uUZa*glRhHd1a#fF$GNj8I zJ)1y5 zRq5(_sIyg1x&?he-&>Z%&2fZjZDRe_o2m~%usm-v(M$3>wFh_m2a7?gFqbOa>KVYz zh})DXZ%f-RJfQLEbEl_lUkXHfBcK}t@FSq54)c}VNkYkti=Hoo5E>E*`p~O$7;n#$ zZI>pb98QNfXW-BrQ*i-cmt}x-IOmRBj7WHKqo+Q*L?&1*5<5{MB}>zkCWn|JO#L?y$<&2)as}NC%i(0EX4!yGMeLfZ&`V2WpweNXFtWQX_}PFah<@02{$c+au?`NkD-D1~0(j2)>Ze<7WhuSHI%g;(Z^qjj|GO*3ev_g&o; zxK<$a*z^3+56eVQMXiZJL0%Yv6y#DAqG~I9_oQMJ5OZ6v^mUuwrImF3BVwqCOaLJiDZ z3z-RZBmplaQi0v^yBw*A+2n0f*p>5B14>ErCV$%X*Smk<|Ml!0MYo>$wDuw6?UUcP zcCqPavpw~?e*%BKTezv`Sxv1RJC3uTQC!*srI{cBM4N@Lf+52IWK2vWOaTrtAP9j5 zT*5AnqwZkXDLGrdgtOLz5u&r>?Jub8pCdS=0v01Zq>~ni_9VJ*3NPj&MI-`v!Cm)2 z$gz{X7)k}1O5K_8lx9Q6GAnBO&x*%n!nku9clfkJxk}#b$g3{Tg*xvjFaTeWb}|Pd z*L@$sLl!xLNEmz&+#L=Oa_l4*Lx~_mWX!qT?}LH@O~1(%oFou=(f3W^#ayI_L;x?i z+aDq1*hwyi5^AKcw`NSztunL zmIA;40ssW?KkU*Ue{@hPCWpcElZd_q;D=@c@wItJl!e3!;jMcFV|fn@V}!OiNqJNYw=`juRiJmK5|0V9H? zPTIVHZcZ8Xy|iP5o9n(_JvbH-Wtg|W0kK&cV^gWf-(ONF+WbPWC_y}%i=iOsX!nz# z!vQ=FL4=^ga~^Qu6N$-4_owj0A`<$;AE7fn=_Jy)j*E^R@l0ZF(oLh z_U-oW*=wj0z?BUfP8mZ+@Fa>c64aT*c)l4$@5DudU=|Tvml3&}Znset4|$0Z0T2N6 zk*#^i-dD?Yu{c4KA@FcYeAkbN^oq*2;RHm0NqJg>Oo(qHS*XCC;XnWy$pb5Nkzd{(V_NN^-N=#4yuLvV> z3LEhJcFV7JPC}JM-~Sc1g7wyC1_RI+Y>@D47=Xc3mYcsJa;!mg0`Nv%b~_9NBDO=o zfz$19=%u(F0WuQXF`$dF?MRCX17X)=QIPBgabggFmPQlW%mhx9CBp)lX2_4drb`iL zu7rUk$@0JG*lD&rPAo;PVBzo4~vSeE*H?@%C%gH6= z0SQfEBIv5kk}5%*(DzhOJDFAB`bCWE=qE|><_g4)xt4KA(&V^`jF*;%( zCSrjJ)7XfUSzPLwyX3Mf@$kFa6oM?s(jd$-O6uz0xofUVYGXGNlhAK# zK^-cXw8LyN>MdJ#88b!lsxzdJWMrM2q3Z zqee}dwP@AWpdsy!JK>}z&3g1Aq0y&ijaAlLXM^Rn>uA1LZPTT@6}nOKZ!1+`dGCW$ zM8u{lw?Hk=dZLbDV$L8Z_T2NeJZj9i0}h%n>5wUhdj`T@fv|s|udVZKa-X-2?zrcH zyT<&uxBnmP`v-dg`mHq>j>ePeY`$2o)|>5ae>k4bm+S5Rc)s4=KR&;{e}4b|rK)-B zW3GMf`_PB6?Q+~6Uw-(-%eUY1+kVII`aKM?E}TMeJiR70<;XXqYZ$P9jP&bgJT{5r z5VUAH#)>I<%-=AH3W=!`i!#oV)ZH3$ltK@6${vXYi>%&0Eo@o@A&n)Hn;Gj0Jv*$6 zdT@FtQ_#65T(_Vr z_6(Q8FAu_{d2viB*iFH$fWPsrNW-rpsI&Lx#I5eclw@(rWq;z~mb>EHX9>p_d!d@R zoiiy@5=-9uTepzzNy|_|3)=DvWn{MqixP8^!Z5GLVguUClgP^81D=#y1$1xE&`z*B zGJMAo#- z`Jwwy*$5tfGgn?mJVm@JnlKsg&9vT!q6&MG2=XNo)|N%mcCV%a0kO;Qifv~^(!+mW@ZYIU_!Z@hr`TO zTe7K+qU+R@XBmuAiYUAaziD+{J>SpM#CjIeSml8qXRXH#Zd;CMm_8;9ReHmDv9H$# zsVFxZ0%33lopG!KEipGXS}RG7bd+Rt=BKAK!uKc2s=G8MZe7JMn8ISOZdt^XaCE&c!wDU zIlIl2=ONvc@b!PL|L&(tNMu=OsL;B-sbDS2DVl=T4O-wRNF;;(n5iZVRFtVv5vEI+MiZuhnE|tqFh!@J z2GIu5ZwNia^bpeq(QgR-hR{PyxtP#^t+-@Ba#g^b0xRXCT?D8CpkM>g002Y*K*0(C zfCg*@KmyDuuqtFMKmt?&P_O}L005!@pkM`noTl6NI8sTD+EtaOKlJf;g~VBU=!wp_ zEDkOnxjfndjbY{dF_+rBTYXYEE;&P`rKnMDU8(kD` Date: Tue, 1 Oct 2024 12:15:17 +0200 Subject: [PATCH 167/241] sepolia: use gasMultiplier to prevent random failures during deployment --- contracts-v1/hardhat.config.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/contracts-v1/hardhat.config.ts b/contracts-v1/hardhat.config.ts index e04272ba65..b871bd8b92 100644 --- a/contracts-v1/hardhat.config.ts +++ b/contracts-v1/hardhat.config.ts @@ -93,6 +93,7 @@ const config: HardhatUserConfig = { accounts: [TESTNET_DEPLOYER_PRIVATE_KEY, TESTNET_MULTISIG_PRIVATE_KEY], chainId: 11155111, url: TESTNET_RPC_URL, + gasMultiplier: 2, }, }, solidity: { From 490ef0db68fbe5dc632e64766d31533f84ced788 Mon Sep 17 00:00:00 2001 From: Housekeeper Bot Date: Tue, 1 Oct 2024 10:47:55 +0000 Subject: [PATCH 168/241] [CI/CD] Update uat.env contracts --- ci/argocd/contracts/uat.env | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/ci/argocd/contracts/uat.env b/ci/argocd/contracts/uat.env index 17c6428778..1c7452eaba 100644 --- a/ci/argocd/contracts/uat.env +++ b/ci/argocd/contracts/uat.env @@ -1,8 +1,8 @@ -BLOCK_NUMBER=6768112 +BLOCK_NUMBER=6793660 GLM_CONTRACT_ADDRESS=0x71432DD1ae7DB41706ee6a22148446087BdD0906 -AUTH_CONTRACT_ADDRESS=0x06CDA8700151efDF0C5d97DCb2fA69e202E8c2c5 -DEPOSITS_CONTRACT_ADDRESS=0x1C6aA9F059E6d8C9dC3771E8544EEd44Ab0fEE73 -EPOCHS_CONTRACT_ADDRESS=0xB16F3fcEE044d6fa56385dDAC822eF8Ff1f1785c -PROPOSALS_CONTRACT_ADDRESS=0x7e948e67364965d88f150E11cf172267385ab858 -WITHDRAWALS_TARGET_CONTRACT_ADDRESS=0x394138d77Ea17C5E34297599Ec44035b6d38029F -VAULT_CONTRACT_ADDRESS=0xFe8358a2EdbA6E8048D61c24DC21dae2Bd42A786 +AUTH_CONTRACT_ADDRESS=0xa49b0d8711BDe73C4c48371bE53B3a0Ba9efFDa0 +DEPOSITS_CONTRACT_ADDRESS=0x773B267Eb3BfdC0C00b6d5e6ED0AFC8dF164E69b +EPOCHS_CONTRACT_ADDRESS=0x227c5ad3cD9Ba8646e1584e822F88538037e85b7 +PROPOSALS_CONTRACT_ADDRESS=0x322FA3313CDDE6e021db71D6Ea8aaaF57fE219f2 +WITHDRAWALS_TARGET_CONTRACT_ADDRESS=0xc0720c9cC73497A142E7D0a4A05cFF2DAf82b3fc +VAULT_CONTRACT_ADDRESS=0xff21F0910F7A3b83D2D4d145C648A738c48d993F From 88569f864cdb2047d4fdd2c6e230be40698b7313 Mon Sep 17 00:00:00 2001 From: Pawel Peregud Date: Tue, 1 Oct 2024 13:29:05 +0200 Subject: [PATCH 169/241] localenv: don't rely on fancy 'new' features of docker --- ci/Dockerfile.multideployer | 6 ++++-- localenv/multideployer/Dockerfile | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/ci/Dockerfile.multideployer b/ci/Dockerfile.multideployer index cae93cd928..16ed6724fb 100644 --- a/ci/Dockerfile.multideployer +++ b/ci/Dockerfile.multideployer @@ -9,8 +9,10 @@ WORKDIR /app RUN mkdir /hardhat/ COPY --from=hardhat /app/ /hardhat/ -COPY --chmod=+x entrypoint.sh . -COPY --chmod=+x wait_for_subgraph.sh . +COPY entrypoint.sh . +RUN chmod +x ./entrypoint.sh +COPY wait_for_subgraph.sh . +RUN chmod +x ./wait_for_subgraph.sh COPY server.py /app/server.py ENTRYPOINT ["./entrypoint.sh"] diff --git a/localenv/multideployer/Dockerfile b/localenv/multideployer/Dockerfile index a4ee41711a..00da4d93bc 100644 --- a/localenv/multideployer/Dockerfile +++ b/localenv/multideployer/Dockerfile @@ -11,8 +11,10 @@ RUN npx hardhat compile WORKDIR /app/ -COPY --chmod=+x entrypoint.sh . -COPY --chmod=+x wait_for_subgraph.sh . +COPY entrypoint.sh . +RUN chmod +x ./entrypoint.sh +COPY wait_for_subgraph.sh . +RUN chmod +x ./wait_for_subgraph.sh COPY server.py /app/server.py ENTRYPOINT ["./entrypoint.sh"] From 79d325e9cfcd0b7a14e8d6fced36fcdfedf5d09a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Zi=C3=B3=C5=82ek?= Date: Tue, 1 Oct 2024 14:21:34 +0200 Subject: [PATCH 170/241] feat: prototype in progress --- .../AllocationLowUqScore.tsx | 70 ++++++++++++++++--- .../Allocation/AllocationLowUqScore/types.ts | 1 + .../ModalAllocationLowUqScore.tsx | 2 +- .../HomeGridRewardsEstimatorUqSelector.tsx | 1 + .../Home/HomeGridUQScore/HomeGridUQScore.tsx | 6 +- .../CalculatingUQScore/CalculatingUQScore.tsx | 8 +-- client/src/constants/uq.ts | 1 + client/src/locales/en/translation.json | 24 ++++--- client/src/services/toastService.ts | 1 + 9 files changed, 89 insertions(+), 25 deletions(-) create mode 100644 client/src/constants/uq.ts diff --git a/client/src/components/Allocation/AllocationLowUqScore/AllocationLowUqScore.tsx b/client/src/components/Allocation/AllocationLowUqScore/AllocationLowUqScore.tsx index 9e91b6d674..7b8564bcdb 100644 --- a/client/src/components/Allocation/AllocationLowUqScore/AllocationLowUqScore.tsx +++ b/client/src/components/Allocation/AllocationLowUqScore/AllocationLowUqScore.tsx @@ -1,21 +1,75 @@ -import React, { FC, useState } from 'react'; +import React, { FC, useEffect, useState } from 'react'; import { Trans, useTranslation } from 'react-i18next'; -import { useNavigate } from 'react-router-dom'; +import { useAccount } from 'wagmi'; import BoxRounded from 'components/ui/BoxRounded'; import Button from 'components/ui/Button'; import InputCheckbox from 'components/ui/InputCheckbox'; -import { ROOT_ROUTES } from 'routes/RootRoutes/routes'; +import { GITCOIN_PASSPORT_CUSTOM_OCTANT_DASHBOARD } from 'constants/urls'; +import useRefreshAntisybilStatus from 'hooks/mutations/useRefreshAntisybilStatus'; +import useCurrentEpoch from 'hooks/queries/useCurrentEpoch'; +import useUqScore from 'hooks/queries/useUqScore'; +import toastService from 'services/toastService'; import styles from './AllocationLowUqScore.module.scss'; import AllocationLowUqScoreProps from './types'; -const AllocationLowUqScore: FC = ({ onAllocate }) => { +const AllocationLowUqScore: FC = ({ onAllocate, onCloseModal }) => { const { t } = useTranslation('translation', { keyPrefix: 'components.allocation.lowUQScoreModal', }); const [isChecked, setIsChecked] = useState(false); - const navigate = useNavigate(); + + const { address } = useAccount(); + + const { data: currentEpoch } = useCurrentEpoch(); + const { + data: uqScore, + refetch: refetchUqScore, + isFetching: isFetchingUqScore, + } = useUqScore(currentEpoch!); + const { mutateAsync: refreshAntisybilStatus, isPending: isPendingRefreshAntisybilStatus } = + useRefreshAntisybilStatus(); + + const windowOnFocus = () => { + if (!address) { + return; + } + refreshAntisybilStatus(address).then(() => { + refetchUqScore(); + // And then what? Discussion in Linear. + }); + }; + + const windowOnBlur = () => setInterval(() => {}); + + useEffect(() => { + window.addEventListener('focus', windowOnFocus); + window.addEventListener('blur', windowOnBlur); + + return () => { + window.removeEventListener('focus', windowOnFocus); + window.removeEventListener('blur', windowOnBlur); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useEffect(() => { + // When user manages to increase their score to what gives UQ score of 100, close the modal. + if (!uqScore || uqScore < 100n) { + return; + } + toastService.showToast({ + message: t('toasts.success.message'), + name: 'uqScoreSuccessfullyIncreased', + title: t('toasts.success.title'), + type: 'success', + }); + onCloseModal(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [uqScore]); + + const isFetching = isFetchingUqScore || isPendingRefreshAntisybilStatus; return ( <> @@ -33,12 +87,12 @@ const AllocationLowUqScore: FC = ({ onAllocate }) =>

- - {isAddToAllocateButtonVisible && ( - onAddRemoveFromAllocate(address)} - /> - )}
+ + onAddRemoveFromAllocate(address)} + variant="cta" + />
- - {name} - -
); }; diff --git a/client/src/components/shared/ButtonAddToAllocate/ButtonAddToAllocate.module.scss b/client/src/components/shared/ButtonAddToAllocate/ButtonAddToAllocate.module.scss index dc3ce36d05..878d38722b 100644 --- a/client/src/components/shared/ButtonAddToAllocate/ButtonAddToAllocate.module.scss +++ b/client/src/components/shared/ButtonAddToAllocate/ButtonAddToAllocate.module.scss @@ -1,41 +1,33 @@ .root { z-index: $z-index-4; + padding: 0; - &.isAllocatedTo { - svg path { - stroke: $color-white; - // fill: $color-white; -- no fill here, it makes the stroke bigger. - } - } + &.variant--cta { + min-width: 15rem; + font-size: $font-size-12; + padding: 0 1rem 0 1.6rem; + justify-content: right; + background-color: $color-octant-dark; + cursor: pointer; - &:not(.isAllocatedTo) { - &.isAddedToAllocate { - svg path { - stroke: $color-octant-orange; - fill: $color-octant-orange; - } + &:hover { + background-color: $color-octant-grey10; } - } - .svgWrapper { - display: flex; - } - - &.isArchivedProject { - &:hover { - background: transparent; - cursor: default; + &:active { + background-color: $color-octant-dark; } - svg path { - stroke: $color-octant-grey1; + &.isDisabled { + cursor: default; + color: $color-octant-grey1; + background-color: $color-octant-grey2; } - &.isAllocatedTo { - svg circle { - stroke: $color-octant-grey5; - fill: $color-octant-grey5; - } + .wrapper { + display: flex; + align-items: center; + margin: 0 auto; } } } diff --git a/client/src/components/shared/ButtonAddToAllocate/ButtonAddToAllocate.tsx b/client/src/components/shared/ButtonAddToAllocate/ButtonAddToAllocate.tsx index 4d39874288..6a8f96f6ff 100644 --- a/client/src/components/shared/ButtonAddToAllocate/ButtonAddToAllocate.tsx +++ b/client/src/components/shared/ButtonAddToAllocate/ButtonAddToAllocate.tsx @@ -3,11 +3,10 @@ import { useAnimate } from 'framer-motion'; import React, { FC, useMemo, useState, memo } from 'react'; import { useTranslation } from 'react-i18next'; +import ButtonAddToAllocateIcon from 'components/shared/ButtonAddToAllocate/ButtonAddToAllocateIcon'; import Button from 'components/ui/Button'; -import Svg from 'components/ui/Svg'; import Tooltip from 'components/ui/Tooltip'; import useIsPatronMode from 'hooks/queries/useIsPatronMode'; -import { checkMark, heart } from 'svg/misc'; import styles from './ButtonAddToAllocate.module.scss'; import ButtonAddToAllocateProps from './types'; @@ -19,6 +18,7 @@ const ButtonAddToAllocate: FC = ({ isAddedToAllocate, isAllocatedTo, isArchivedProject, + variant = 'iconOnly', }) => { const { i18n, t } = useTranslation('translation', { keyPrefix: 'components.dedicated.buttonAddToAllocate', @@ -27,6 +27,9 @@ const ButtonAddToAllocate: FC = ({ const { data: isPatronMode } = useIsPatronMode(); const [isTooltipClicked, setIsTooltipClicked] = useState(false); const [isTooltipVisible, setIsTooltipVisible] = useState(false); + + const isDisabled = isArchivedProject || isPatronMode; + const tooltipText = useMemo(() => { if (isArchivedProject && isAllocatedTo) { return i18n.t('common.donated'); @@ -44,6 +47,12 @@ const ButtonAddToAllocate: FC = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [isAddedToAllocate, isTooltipClicked, isArchivedProject, isAllocatedTo]); + const ctaButtonText = useMemo(() => { + if (isAllocatedTo) {return i18n.t('common.donated');} + if (isAddedToAllocate && !isArchivedProject) {return t('savedProject');} + return t('saveProject'); + }, [isAddedToAllocate, isAllocatedTo, isArchivedProject]); + const handleTooltipVisibilityChange = (isVisible: boolean) => { setIsTooltipVisible(isVisible); if (!isVisible) { @@ -51,41 +60,69 @@ const ButtonAddToAllocate: FC = ({ } }; + const animateHeartIcon = () => { + animate(scope?.current, { scale: [1.2, 1] }, { duration: 0.25, ease: 'easeIn' }); + }; + return ( ); }; diff --git a/client/src/components/shared/ButtonAddToAllocate/ButtonAddToAllocateIcon/ButtonAddToAllocateIcon.module.scss b/client/src/components/shared/ButtonAddToAllocate/ButtonAddToAllocateIcon/ButtonAddToAllocateIcon.module.scss new file mode 100644 index 0000000000..fc32498eb0 --- /dev/null +++ b/client/src/components/shared/ButtonAddToAllocate/ButtonAddToAllocateIcon/ButtonAddToAllocateIcon.module.scss @@ -0,0 +1,43 @@ +.root { + display: flex; + + &.isDisabled { + stroke: $color-octant-grey1; + } + + &.isAllocatedTo { + svg path { + stroke: $color-white; + // fill: $color-white; -- no fill here, it makes the stroke bigger. + } + } + + &:not(.isAllocatedTo) { + &:not(.isArchivedProject) { + &.isAddedToAllocate { + svg path { + stroke: $color-octant-orange; + fill: $color-octant-orange; + } + } + } + } + + &.isArchivedProject { + &:hover { + background: transparent; + cursor: default; + } + + svg path { + stroke: $color-octant-grey1; + } + + &.isAllocatedTo { + svg circle { + stroke: $color-octant-grey5; + fill: $color-octant-grey5; + } + } + } +} diff --git a/client/src/components/shared/ButtonAddToAllocate/ButtonAddToAllocateIcon/ButtonAddToAllocateIcon.tsx b/client/src/components/shared/ButtonAddToAllocate/ButtonAddToAllocateIcon/ButtonAddToAllocateIcon.tsx new file mode 100644 index 0000000000..3a90866c84 --- /dev/null +++ b/client/src/components/shared/ButtonAddToAllocate/ButtonAddToAllocateIcon/ButtonAddToAllocateIcon.tsx @@ -0,0 +1,28 @@ +import cx from 'classnames'; +import React, { forwardRef } from 'react'; + +import Svg from 'components/ui/Svg'; +import { checkMark, heart } from 'svg/misc'; + +import styles from './ButtonAddToAllocateIcon.module.scss'; +import ButtonAddToAllocateIconProps from './types'; + +const ButtonAddToAllocateIcon = ( + { isDisabled, isAllocatedTo, isArchivedProject, isAddedToAllocate }, + ref, +) => ( +
+ +
+); + +export default forwardRef(ButtonAddToAllocateIcon); diff --git a/client/src/components/shared/ButtonAddToAllocate/ButtonAddToAllocateIcon/index.tsx b/client/src/components/shared/ButtonAddToAllocate/ButtonAddToAllocateIcon/index.tsx new file mode 100644 index 0000000000..5a0b3d9c03 --- /dev/null +++ b/client/src/components/shared/ButtonAddToAllocate/ButtonAddToAllocateIcon/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './ButtonAddToAllocateIcon'; diff --git a/client/src/components/shared/ButtonAddToAllocate/ButtonAddToAllocateIcon/types.ts b/client/src/components/shared/ButtonAddToAllocate/ButtonAddToAllocateIcon/types.ts new file mode 100644 index 0000000000..3aefccd1b8 --- /dev/null +++ b/client/src/components/shared/ButtonAddToAllocate/ButtonAddToAllocateIcon/types.ts @@ -0,0 +1,6 @@ +export default interface ButtonAddToAllocateIconProps { + isAddedToAllocate: boolean; + isAllocatedTo: boolean; + isArchivedProject: boolean; + isDisabled: boolean; +} diff --git a/client/src/components/shared/ButtonAddToAllocate/types.ts b/client/src/components/shared/ButtonAddToAllocate/types.ts index 13c512bcc6..9262a7f52b 100644 --- a/client/src/components/shared/ButtonAddToAllocate/types.ts +++ b/client/src/components/shared/ButtonAddToAllocate/types.ts @@ -5,4 +5,5 @@ export default interface ButtonAddToAllocateProps { isAllocatedTo: boolean; isArchivedProject?: boolean; onClick: () => void; + variant?: 'cta' | 'iconOnly'; } diff --git a/client/src/components/shared/Layout/Layout.module.scss b/client/src/components/shared/Layout/Layout.module.scss index 66677363cc..d7408586de 100644 --- a/client/src/components/shared/Layout/Layout.module.scss +++ b/client/src/components/shared/Layout/Layout.module.scss @@ -11,6 +11,14 @@ padding: 0 $layout-horizontal-padding-large; } + &.isProjectView { + padding: 0; + + @media #{$desktop-up} { + padding: 0 $layout-horizontal-padding-large; + } + } + .section { width: 100%; diff --git a/client/src/components/shared/Layout/Layout.tsx b/client/src/components/shared/Layout/Layout.tsx index deab256c89..a43318fb2b 100644 --- a/client/src/components/shared/Layout/Layout.tsx +++ b/client/src/components/shared/Layout/Layout.tsx @@ -1,5 +1,6 @@ import cx from 'classnames'; import React, { FC, Fragment, useEffect, useRef } from 'react'; +import { useLocation } from 'react-router-dom'; import LayoutFooter from 'components/shared/Layout/LayoutFooter'; import LayoutNavbar from 'components/shared/Layout/LayoutNavbar'; @@ -11,6 +12,7 @@ import { LAYOUT_BODY_ID } from 'constants/domElementsIds'; import useIsProjectAdminMode from 'hooks/helpers/useIsProjectAdminMode'; import useMediaQuery from 'hooks/helpers/useMediaQuery'; import useIsPatronMode from 'hooks/queries/useIsPatronMode'; +import { ROOT_ROUTES } from 'routes/RootRoutes/routes'; import useLayoutStore from 'store/layout/store'; import SyncView from 'views/SyncView'; @@ -34,6 +36,9 @@ const Layout: FC = ({ const topBarWrapperRef = useRef(null); const scrollRef = useRef(window.scrollY); const lastScrollYUpRef = useRef(0); + const { pathname } = useLocation(); + + const isProjectView = pathname.includes(`${ROOT_ROUTES.project.absolute}/`); const { setIsWalletModalOpen, @@ -99,7 +104,11 @@ const Layout: FC = ({ return ( -
+
= ({ epoch, numberOfDonors, totalValueOfAllocations, + matchedRewards, + donations, + showMoreInfo, }) => { const { t, i18n } = useTranslation('translation', { keyPrefix: 'components.dedicated.projectRewards', }); + const { isLargeDesktop, isDesktop } = useMediaQuery(); const getValuesToDisplay = useGetValuesToDisplay(); const isArchivedProject = epoch !== undefined; - const currentTotalIncludingMFForProjectsAboveThreshold = getValuesToDisplay({ + const currentTotalIncludingMFForProjectsAboveThresholdToDisplay = getValuesToDisplay({ cryptoCurrency: 'ethereum', showCryptoSuffix: true, valueCrypto: totalValueOfAllocations, - }); + }).primary; + + const matchFundingToDisplay = getValuesToDisplay({ + cryptoCurrency: 'ethereum', + showCryptoSuffix: true, + valueCrypto: matchedRewards, + }).primary; + + const donationsToDisplay = getValuesToDisplay({ + cryptoCurrency: 'ethereum', + showCryptoSuffix: true, + valueCrypto: donations, + }).primary; const leftSectionLabel = useMemo(() => { if (!isArchivedProject) { @@ -37,18 +54,36 @@ const RewardsWithoutThreshold: FC = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [isArchivedProject]); + const showVerticalDividers = showMoreInfo && (isLargeDesktop || isDesktop); + return (
-
+
{leftSectionLabel}
- {currentTotalIncludingMFForProjectsAboveThreshold.primary} + {currentTotalIncludingMFForProjectsAboveThresholdToDisplay}
+ {showMoreInfo && ( + <> +
+
+
{i18n.t('common.donations')}
+
{donationsToDisplay}
+
+
+
+
+
{i18n.t('common.matchFunding')}
+
{matchFundingToDisplay}
+
+
+ + )}
{i18n.t('common.donors')}
{numberOfDonors}
diff --git a/client/src/components/shared/RewardsWithoutThreshold/types.ts b/client/src/components/shared/RewardsWithoutThreshold/types.ts index 4aad04d66e..c25c7e2ae6 100644 --- a/client/src/components/shared/RewardsWithoutThreshold/types.ts +++ b/client/src/components/shared/RewardsWithoutThreshold/types.ts @@ -1,8 +1,11 @@ import { ProjectIpfsWithRewards } from 'hooks/queries/useProjectsIpfsWithRewards'; -export default interface RewardsWithoutThreshold { +export default interface RewardsWithoutThresholdProps { className?: string; + donations?: ProjectIpfsWithRewards['donations']; epoch?: number; + matchedRewards?: ProjectIpfsWithRewards['matchedRewards']; numberOfDonors: ProjectIpfsWithRewards['numberOfDonors']; + showMoreInfo?: boolean; totalValueOfAllocations: ProjectIpfsWithRewards['totalValueOfAllocations']; } diff --git a/client/src/components/ui/Button/Button.module.scss b/client/src/components/ui/Button/Button.module.scss index 533d04a2f3..b401995c2e 100644 --- a/client/src/components/ui/Button/Button.module.scss +++ b/client/src/components/ui/Button/Button.module.scss @@ -49,7 +49,8 @@ $paddingVertical: 1rem; } .variant-- { - &cta, &cta2 { + &cta, + &cta2 { color: $color-white; border: 0.1rem solid $color-octant-dark; background: $color-octant-dark; @@ -184,7 +185,8 @@ $paddingVertical: 1rem; min-height: initial; } - &link, &link6 { + &link, + &link6 { font-size: $font-size-12; border-radius: 0; min-height: initial; @@ -202,10 +204,10 @@ $paddingVertical: 1rem; } &link6:hover .icon { - position: relative; - bottom: 0.1rem; - left: 0.1rem; - } + position: relative; + bottom: 0.1rem; + left: 0.1rem; + } &link3 { position: relative; @@ -255,7 +257,7 @@ $paddingVertical: 1rem; &:not(.isIconVariant) { &.isOnLeft { - margin: 0 1.2rem 0 0; + margin: 0 1rem 0 0; } &.isOnRight { diff --git a/client/src/locales/en/translation.json b/client/src/locales/en/translation.json index eec5bb4fa3..c632353e7e 100644 --- a/client/src/locales/en/translation.json +++ b/client/src/locales/en/translation.json @@ -230,7 +230,9 @@ "saveToAllocate": "Save to allocate", "saved": "Saved", "removeFromAllocate": "Remove from allocate", - "removed": "Removed" + "removed": "Removed", + "saveProject": "Save project", + "savedProject": "Saved project" }, "allocationNavigation": { "confirm": "Confirm", @@ -454,7 +456,8 @@ }, "project": { "loadingProblem": "Loading of this project encountered a problem.", - "backToTop": "Back to Top" + "backToTop": "Back to Top", + "share": "Share" }, "projects": { "projectsTimelineWidget": { diff --git a/client/src/styles/utils/_typography.scss b/client/src/styles/utils/_typography.scss index df34cee721..85614fc8e6 100644 --- a/client/src/styles/utils/_typography.scss +++ b/client/src/styles/utils/_typography.scss @@ -3,6 +3,7 @@ $font-size-48: 4.8rem; $font-size-40: 4rem; $font-size-32: 3.2rem; $font-size-24: 2.4rem; +$font-size-22: 2.2rem; $font-size-20: 2rem; $font-size-18: 1.8rem; $font-size-16: 1.6rem; diff --git a/client/src/views/ProjectView/ProjectView.module.scss b/client/src/views/ProjectView/ProjectView.module.scss index 09c13a731f..c6582b36cb 100644 --- a/client/src/views/ProjectView/ProjectView.module.scss +++ b/client/src/views/ProjectView/ProjectView.module.scss @@ -14,3 +14,10 @@ align-items: center; justify-content: center; } + +.initialLoader { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} diff --git a/client/src/views/ProjectView/ProjectView.tsx b/client/src/views/ProjectView/ProjectView.tsx index 7b347aaa69..3c90bc9f38 100644 --- a/client/src/views/ProjectView/ProjectView.tsx +++ b/client/src/views/ProjectView/ProjectView.tsx @@ -7,7 +7,6 @@ import { Navigate, Route, Routes, useParams } from 'react-router-dom'; import ProjectBackToTopButton from 'components/Project/ProjectBackToTopButton'; import ProjectList from 'components/Project/ProjectList'; -import Layout from 'components/shared/Layout'; import Loader from 'components/ui/Loader'; import useAreCurrentEpochsProjectsHiddenOutsideAllocationWindow from 'hooks/helpers/useAreCurrentEpochsProjectsHiddenOutsideAllocationWindow'; import useCurrentEpoch from 'hooks/queries/useCurrentEpoch'; @@ -123,7 +122,7 @@ const ProjectView = (): ReactElement => { }, [loadedProjects.length, projectsIpfsWithRewards.length]); if (!initialElement || !areMatchedProjectsReady || projectsIpfsWithRewards.length === 0) { - return ; + return ; } if ( From c7a8ee5f6356477929cbc30085f31b6c19817c09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Miko=C5=82ajczyk?= Date: Wed, 2 Oct 2024 10:29:13 +0200 Subject: [PATCH 174/241] oct-2009: project view update 2 --- .../ProjectListItemHeader/ProjectListItemHeader.tsx | 12 +++--------- .../ButtonAddToAllocate/ButtonAddToAllocate.tsx | 9 +++++++-- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/client/src/components/Project/ProjectListItemHeader/ProjectListItemHeader.tsx b/client/src/components/Project/ProjectListItemHeader/ProjectListItemHeader.tsx index f6c18a6701..ec5e98f95c 100644 --- a/client/src/components/Project/ProjectListItemHeader/ProjectListItemHeader.tsx +++ b/client/src/components/Project/ProjectListItemHeader/ProjectListItemHeader.tsx @@ -9,7 +9,6 @@ import Svg from 'components/ui/Svg'; import Tooltip from 'components/ui/Tooltip'; import env from 'env'; import useIdsInAllocation from 'hooks/helpers/useIdsInAllocation'; -import useIsAddToAllocateButtonVisible from 'hooks/helpers/useIsAddToAllocateButtonVisible'; import useCurrentEpoch from 'hooks/queries/useCurrentEpoch'; import useIsDecisionWindowOpen from 'hooks/queries/useIsDecisionWindowOpen'; import useUserAllocations from 'hooks/queries/useUserAllocations'; @@ -65,11 +64,6 @@ const ProjectListItemHeader: FC = ({ return false; }, [address, allocations, userAllocations, epoch, isDecisionWindowOpen]); - const isAddToAllocateButtonVisible = useIsAddToAllocateButtonVisible({ - isAllocatedTo, - isArchivedProject, - }); - const onShareClick = (): boolean | Promise => { const { origin } = window.location; const url = `${origin}${ROOT_ROUTES.project.absolute}/${epochUrl}/${address}`; @@ -110,7 +104,8 @@ const ProjectListItemHeader: FC = ({ {website!.label || website!.url} @@ -122,7 +117,7 @@ const ProjectListItemHeader: FC = ({ text={isLinkCopied ? i18n.t('common.copied') : i18n.t('common.copy')} variant="small" > -
- = ({ }, [isAddedToAllocate, isTooltipClicked, isArchivedProject, isAllocatedTo]); const ctaButtonText = useMemo(() => { - if (isAllocatedTo) {return i18n.t('common.donated');} - if (isAddedToAllocate && !isArchivedProject) {return t('savedProject');} + if (isAllocatedTo) { + return i18n.t('common.donated'); + } + if (isAddedToAllocate && !isArchivedProject) { + return t('savedProject'); + } return t('saveProject'); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [isAddedToAllocate, isAllocatedTo, isArchivedProject]); const handleTooltipVisibilityChange = (isVisible: boolean) => { From cffc9aad7d2a557a75228066a2c0ba1cddcc0222 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Zi=C3=B3=C5=82ek?= Date: Wed, 2 Oct 2024 11:02:06 +0200 Subject: [PATCH 175/241] feat: CR adjustments --- .../Allocation/AllocationLowUqScore/AllocationLowUqScore.tsx | 3 ++- .../HomeGridRewardsEstimatorUqSelector.tsx | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/components/Allocation/AllocationLowUqScore/AllocationLowUqScore.tsx b/client/src/components/Allocation/AllocationLowUqScore/AllocationLowUqScore.tsx index 6cf8f4b448..31fb3c6a5e 100644 --- a/client/src/components/Allocation/AllocationLowUqScore/AllocationLowUqScore.tsx +++ b/client/src/components/Allocation/AllocationLowUqScore/AllocationLowUqScore.tsx @@ -99,7 +99,8 @@ const AllocationLowUqScore: FC = ({ onAllocate, onClo
{to - ? `${i18n.t('common.close')} ${format(to, 'dd MMMM haaa')} CET` + ? `${i18n.t(shouldUseThirdPersonSingularVerb ? 'common.closes' : 'common.close')} ${format(to, 'dd MMMM haaa')} CET` : `${format(from, 'haaa')} CET`}
diff --git a/client/src/components/shared/Layout/LayoutTopBarCalendar/CalendarItem/types.ts b/client/src/components/shared/Layout/LayoutTopBarCalendar/CalendarItem/types.ts index 9cbf051091..f5e740df64 100644 --- a/client/src/components/shared/Layout/LayoutTopBarCalendar/CalendarItem/types.ts +++ b/client/src/components/shared/Layout/LayoutTopBarCalendar/CalendarItem/types.ts @@ -4,5 +4,6 @@ export default interface CalendarItemProps { id: string; isActive: boolean; label: string; + shouldUseThirdPersonSingularVerb?: boolean; to?: Date; } diff --git a/client/src/constants/milestones.ts b/client/src/constants/milestones.ts index a3cf0dc8fa..0095625470 100644 --- a/client/src/constants/milestones.ts +++ b/client/src/constants/milestones.ts @@ -5,6 +5,7 @@ export type Milestone = { href?: string; id: string; label: string; + shouldUseThirdPersonSingularVerb?: boolean; to?: Date; }; @@ -91,12 +92,14 @@ export default function getMilestones(): Milestone[] { from: new Date('2024-01-01T00:00:00+0100'), id: 'e2-snapshot-vote', label: i18n.t('views.projects.projectsTimelineWidget.snapshotVote'), + shouldUseThirdPersonSingularVerb: true, to: new Date('2024-01-05T00:00:00+0100'), }, { from: new Date('2024-01-17T17:00:00+0100'), id: 'e2-allocation-window', label: i18n.t('views.projects.projectsTimelineWidget.allocationWindow'), + shouldUseThirdPersonSingularVerb: true, to: new Date('2024-01-31T17:00:00+0100'), }, { @@ -120,6 +123,7 @@ export default function getMilestones(): Milestone[] { from: new Date('2024-03-18T00:00:00+0100'), id: 'e3-snapshot-vote', label: i18n.t('views.projects.projectsTimelineWidget.snapshotVote'), + shouldUseThirdPersonSingularVerb: true, to: new Date('2024-03-22T00:00:00+0100'), }, { @@ -127,6 +131,7 @@ export default function getMilestones(): Milestone[] { from: new Date('2024-04-16T18:00:00+0200'), id: 'e3-allocation-window', label: i18n.t('views.projects.projectsTimelineWidget.allocationWindow'), + shouldUseThirdPersonSingularVerb: true, to: new Date('2024-04-30T18:00:00+0200'), }, { @@ -150,12 +155,14 @@ export default function getMilestones(): Milestone[] { from: new Date('2024-06-17T00:00:00+0200'), id: 'e4-snapshot-vote', label: i18n.t('views.projects.projectsTimelineWidget.snapshotVote'), + shouldUseThirdPersonSingularVerb: true, to: new Date('2024-06-21T00:00:00+0200'), }, { from: new Date('2024-07-15T18:00:00+0200'), id: 'e4-allocation-window', label: i18n.t('views.projects.projectsTimelineWidget.allocationWindow'), + shouldUseThirdPersonSingularVerb: true, to: new Date('2024-07-29T18:00:00+0200'), }, { @@ -179,12 +186,14 @@ export default function getMilestones(): Milestone[] { from: new Date('2024-08-19T18:00:00+0200'), id: 'e5-snapshot-vote', label: i18n.t('views.projects.projectsTimelineWidget.snapshotVote'), + shouldUseThirdPersonSingularVerb: true, to: new Date('2024-08-25T18:00:00+0200'), }, { from: new Date('2024-10-13T18:00:00+0200'), id: 'e5-allocation-window', label: i18n.t('views.projects.projectsTimelineWidget.allocationWindow'), + shouldUseThirdPersonSingularVerb: true, to: new Date('2024-10-27T17:00:00+0100'), }, { diff --git a/client/src/locales/en/translation.json b/client/src/locales/en/translation.json index 68bf6f2538..000c78b522 100644 --- a/client/src/locales/en/translation.json +++ b/client/src/locales/en/translation.json @@ -26,6 +26,7 @@ "common": { "availableNow": "Available now", "close": "Close", + "closes": "Closes", "copied": "Copied", "copy": "Copy link", "days": "Days", @@ -499,4 +500,4 @@ "information": "We're synchronizing things to prepare the
next epoch, so the app will be unavailable
for a little while. Please check back soon." } } -} +} \ No newline at end of file From 04083b5659ab74c591cff1454f05e5f14c02b5e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Miko=C5=82ajczyk?= Date: Thu, 3 Oct 2024 09:56:45 +0200 Subject: [PATCH 182/241] oct-2021: rewards rate tooltip --- .../Home/HomeRewards/HomeRewards.module.scss | 5 +++++ .../components/Home/HomeRewards/HomeRewards.tsx | 16 +++++++++++++++- client/src/locales/en/translation.json | 3 ++- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/client/src/components/Home/HomeRewards/HomeRewards.module.scss b/client/src/components/Home/HomeRewards/HomeRewards.module.scss index c6abb25ca1..a2d879ed7d 100644 --- a/client/src/components/Home/HomeRewards/HomeRewards.module.scss +++ b/client/src/components/Home/HomeRewards/HomeRewards.module.scss @@ -27,9 +27,14 @@ } .label { + display: flex; font-size: $font-size-10; color: $color-octant-grey5; margin-bottom: 0.2rem; + + .tooltip { + margin-left: 1rem; + } } .value { diff --git a/client/src/components/Home/HomeRewards/HomeRewards.tsx b/client/src/components/Home/HomeRewards/HomeRewards.tsx index 4def7ad09b..fec1eba00b 100644 --- a/client/src/components/Home/HomeRewards/HomeRewards.tsx +++ b/client/src/components/Home/HomeRewards/HomeRewards.tsx @@ -3,6 +3,8 @@ import React, { ReactElement, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { useAccount } from 'wagmi'; +import Svg from 'components/ui/Svg'; +import Tooltip from 'components/ui/Tooltip'; import useEpochPatronsAllEpochs from 'hooks/helpers/useEpochPatronsAllEpochs'; import useGetValuesToDisplay from 'hooks/helpers/useGetValuesToDisplay'; import useIndividualRewardAllEpochs from 'hooks/helpers/useIndividualRewardAllEpochs'; @@ -15,6 +17,7 @@ import useIsDecisionWindowOpen from 'hooks/queries/useIsDecisionWindowOpen'; import useIsPatronMode from 'hooks/queries/useIsPatronMode'; import useMatchedProjectRewards from 'hooks/queries/useMatchedProjectRewards'; import useRewardsRate from 'hooks/queries/useRewardsRate'; +import { questionMark } from 'svg/misc'; import styles from './HomeRewards.module.scss'; @@ -164,7 +167,18 @@ const HomeRewards = (): ReactElement => {
{tiles.map(({ label, value, key, isLoadingValue }) => (
-
{label}
+
+ {label} + {!isProjectAdminMode && !isPatronMode && ( + + + + )} +
{isLoadingValue ? null : value}
diff --git a/client/src/locales/en/translation.json b/client/src/locales/en/translation.json index 68bf6f2538..74a028da74 100644 --- a/client/src/locales/en/translation.json +++ b/client/src/locales/en/translation.json @@ -94,6 +94,7 @@ "totalRewards": "Total rewards", "currentMatchFunding": "Current match funding", "rewardsRate": "Rewards rate", + "rewardsRateTooltip": "Rewards rate is the percentage of your locked GLM you earn as rewards per epoch", "epochTotalMatchFunding": "Epoch total match funding", "epochMF": "Epoch MF" }, @@ -499,4 +500,4 @@ "information": "We're synchronizing things to prepare the
next epoch, so the app will be unavailable
for a little while. Please check back soon." } } -} +} \ No newline at end of file From dd7b2b74968015942d58b86fd87a111ae09388b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Zi=C3=B3=C5=82ek?= Date: Thu, 3 Oct 2024 10:02:30 +0200 Subject: [PATCH 183/241] fix: inputs hover states --- .../ui/InputSelect/InputSelect.module.scss | 22 +++++++++++++++++-- .../ui/InputText/InputText.module.scss | 16 ++++++++++++-- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/client/src/components/ui/InputSelect/InputSelect.module.scss b/client/src/components/ui/InputSelect/InputSelect.module.scss index 5dfc63599a..1a1755cb86 100644 --- a/client/src/components/ui/InputSelect/InputSelect.module.scss +++ b/client/src/components/ui/InputSelect/InputSelect.module.scss @@ -12,6 +12,9 @@ } .selectedValue { + $borderWidth: 0.1rem; + $chevronMarginHorizontal: 1.2rem; + display: flex; align-items: center; align-self: stretch; @@ -20,7 +23,7 @@ justify-content: space-between; .chevron { - margin: 0 1.2rem; + margin: 0 $chevronMarginHorizontal; width: 3.6rem; &.isMenuOpen { @@ -38,13 +41,28 @@ } } &underselect { + $labelPaddingVertical: 0.2rem; + $labelPaddingHorizontal: 1.6rem; + font-size: $font-size-12; font-weight: $font-weight-semibold; background: $color-octant-grey8; border-radius: $border-radius-16; .label { - padding: 0.2rem 1.6rem; + padding: $labelPaddingVertical $labelPaddingHorizontal; + } + + &:hover { + border: $borderWidth solid $color-octant-grey2; + + .chevron { + margin: 0 ($chevronMarginHorizontal - $borderWidth); + } + + .label { + padding: $labelPaddingVertical calc($labelPaddingHorizontal - $borderWidth); + } } } } diff --git a/client/src/components/ui/InputText/InputText.module.scss b/client/src/components/ui/InputText/InputText.module.scss index 78c6952f61..585fde2257 100644 --- a/client/src/components/ui/InputText/InputText.module.scss +++ b/client/src/components/ui/InputText/InputText.module.scss @@ -1,3 +1,5 @@ +$borderWidth: 0.1rem; + .root { position: relative; @@ -147,7 +149,7 @@ .input { height: 4.8rem; padding: 1.6rem 0 1.6rem 1.6rem; - border: 0.1rem solid $color-octant-grey1; + border: $borderWidth solid $color-octant-grey1; border-radius: $border-radius-04; background: $color-white; font-size: $font-size-14; @@ -203,6 +205,8 @@ } &search { + $paddingLeftHasIcon: 4rem; + width: 100%; .input { @@ -214,7 +218,15 @@ color: $color-octant-dark; &.hasIcon { - padding-left: 4rem; + padding-left: $paddingLeftHasIcon; + } + + &:hover { + border: $borderWidth solid $color-octant-grey2; + } + + &.hasIcon:hover { + padding-left: calc($paddingLeftHasIcon - $borderWidth); } &:focus-visible { From 1eab84b9c563526c659781a60c990480d4964735 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Miko=C5=82ajczyk?= Date: Thu, 3 Oct 2024 12:05:11 +0200 Subject: [PATCH 184/241] oct-2019: project tiles padding --- .../Projects/ProjectsList/ProjectsList.module.scss | 14 ++------------ client/src/styles/utils/_mixins.scss | 8 -------- 2 files changed, 2 insertions(+), 20 deletions(-) diff --git a/client/src/components/Projects/ProjectsList/ProjectsList.module.scss b/client/src/components/Projects/ProjectsList/ProjectsList.module.scss index 878d424018..09c652babd 100644 --- a/client/src/components/Projects/ProjectsList/ProjectsList.module.scss +++ b/client/src/components/Projects/ProjectsList/ProjectsList.module.scss @@ -1,5 +1,3 @@ -$elementMargin: 1.6rem; - .noSearchResults { display: flex; flex-direction: column; @@ -21,14 +19,6 @@ $elementMargin: 1.6rem; justify-content: space-between; } -.element { - margin-bottom: $elementMargin; - - @media #{$desktop-up} { - @include flexBasisGutter(2, $elementMargin); - } -} - .epochArchive { width: 100%; background: $color-white; @@ -41,7 +31,7 @@ $elementMargin: 1.6rem; font-size: $font-size-16; font-weight: $font-weight-bold; border-radius: $border-radius-16; - margin: 0 0 1.6rem; + margin: 0 0 1.6rem; .epochDurationLabel { color: $color-octant-grey5; @@ -61,5 +51,5 @@ $elementMargin: 1.6rem; width: 100%; height: 0.1rem; background-color: $color-octant-grey1; - margin: 0 0 1.6rem; + margin: 0 0 1.6rem; } diff --git a/client/src/styles/utils/_mixins.scss b/client/src/styles/utils/_mixins.scss index b2bf8b17b3..c08b7eacac 100644 --- a/client/src/styles/utils/_mixins.scss +++ b/client/src/styles/utils/_mixins.scss @@ -28,14 +28,6 @@ } } -@mixin flexBasisGutter($elementsCount, $gutterSize) { - flex-grow: 0; - flex-shrink: 0; - flex-basis: calc( - #{calc(100% / $elementsCount)} - #{calc($gutterSize * ($elementsCount - 1) / $elementsCount)} - ); -} - @mixin layoutOverflowBlurCommonProperties() { backdrop-filter: blur(2rem); -webkit-backdrop-filter: blur(2rem); From 4c78df591206b121ce1dbada8d97a6b7f067ee1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Miko=C5=82ajczyk?= Date: Thu, 3 Oct 2024 13:13:06 +0200 Subject: [PATCH 185/241] oct-2016: donations empty state update --- .../Home/HomeGridDonations/HomeGridDonations.module.scss | 4 ++++ .../Home/HomeGridDonations/HomeGridDonations.tsx | 9 ++++++++- client/src/locales/en/translation.json | 2 +- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/client/src/components/Home/HomeGridDonations/HomeGridDonations.module.scss b/client/src/components/Home/HomeGridDonations/HomeGridDonations.module.scss index 7a26d25787..3ad82331e5 100644 --- a/client/src/components/Home/HomeGridDonations/HomeGridDonations.module.scss +++ b/client/src/components/Home/HomeGridDonations/HomeGridDonations.module.scss @@ -45,3 +45,7 @@ padding: 0; min-height: 3.2rem; } + +.projectsLink { + font-size: $font-size-14; +} diff --git a/client/src/components/Home/HomeGridDonations/HomeGridDonations.tsx b/client/src/components/Home/HomeGridDonations/HomeGridDonations.tsx index aef7f49226..4eefa3406f 100644 --- a/client/src/components/Home/HomeGridDonations/HomeGridDonations.tsx +++ b/client/src/components/Home/HomeGridDonations/HomeGridDonations.tsx @@ -10,6 +10,7 @@ import useUserAllocationsAllEpochs from 'hooks/helpers/useUserAllocationsAllEpoc import useCurrentEpoch from 'hooks/queries/useCurrentEpoch'; import useIsDecisionWindowOpen from 'hooks/queries/useIsDecisionWindowOpen'; import useUserAllocations from 'hooks/queries/useUserAllocations'; +import { ROOT_ROUTES } from 'routes/RootRoutes/routes'; import useAllocationsStore from 'store/allocations/store'; import useLayoutStore from 'store/layout/store'; @@ -60,7 +61,6 @@ const HomeGridDonations: FC = ({ className }) => { } titleSuffix={ isDecisionWindowOpen ? ( - // TODO: open allocation drawer in edit mode -> https://linear.app/golemfoundation/issue/OCT-1907/allocate-drawer
Date: Fri, 4 Oct 2024 10:22:55 +0200 Subject: [PATCH 189/241] oct-2027: mobile navbar fix --- client/src/components/shared/Layout/Layout.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/components/shared/Layout/Layout.tsx b/client/src/components/shared/Layout/Layout.tsx index a43318fb2b..459330a89d 100644 --- a/client/src/components/shared/Layout/Layout.tsx +++ b/client/src/components/shared/Layout/Layout.tsx @@ -61,7 +61,7 @@ const Layout: FC = ({ const topBarWrapperEl = topBarWrapperRef.current; const listener = e => { - if (e.target.body.className === 'bodyFixed') { + if (e.target.body.className === 'bodyFixed' || window.scrollY < 0) { return; } const { offsetTop, clientHeight } = topBarWrapperEl; From c9f8937790377c36f68b6f8f672797f3806f0e77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Zi=C3=B3=C5=82ek?= Date: Fri, 4 Oct 2024 10:36:32 +0200 Subject: [PATCH 190/241] fix: OCT-2035 & OCT-2024 --- client/src/locales/en/translation.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/locales/en/translation.json b/client/src/locales/en/translation.json index 6aec7f22e6..47fd245937 100644 --- a/client/src/locales/en/translation.json +++ b/client/src/locales/en/translation.json @@ -141,7 +141,7 @@ }, "modalCalculatingYourUniqueness": { "calculatingYourUniqueness": "Calculating your uniqueness", - "calculatingYourUniquenessStep1": "To prove your uniqueness your need a <0>Gitcoin\u00a0Passport score of 15 or higher.

If you have this your donations will attract the maximum amount of match funding. If not, maximum match funding will be set to 20%.

You can increase your score in a couple of different ways.", + "calculatingYourUniquenessStep1": "To prove your uniqueness your need a <0>Gitcoin\u00a0Passport score of 15 or higher.

If you have this your donations will attract the maximum amount of match funding. If not, maximum match funding will be set to 10%.

You can increase your score in a couple of different ways.", "calculatingYourUniquenessStep2": "You can go to our <0>Passport dashboard and add stamps to the score for your Octant address.

If your Passport score is not on your primary address, you can delegate your score from another address with a 15+ Passport score.

You can only do this once, and it must be done before allocating to benefit the current epoch.", "calculatingYourUniquenessStep3": "Delegation will not link your addresses or compromise your privacy in any way.

We require proof of uniqueness to defend against sybil attacks as we have switched to a quadratic funding model.

To learn more, check out Gitcoin’s handy guide on improving your score." }, @@ -450,7 +450,7 @@ "stepsDecisionWindowClosed": { "welcomeToOctant": { "header": "Welcome to Octant Epoch {{epoch}}", - "text": "Octant is a Golem Foundation experiment in decentralised governance and funding public good projects.

To get started, lock some GLM in the Home view, and explore the projects for this epoch to get an idea of what Octant is all" + "text": "Octant is a Golem Foundation experiment in decentralised governance and funding public good projects.

To get started, lock some GLM in the Home view, and explore the projects for this epoch to get an idea of what Octant is all about." }, "earnRewards": { "header": "Earn ETH rewards", From c3d0bfb570e2163ed1c86d72b27dae8ddd21f952 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Zi=C3=B3=C5=82ek?= Date: Fri, 4 Oct 2024 11:11:04 +0200 Subject: [PATCH 191/241] feat: search working --- client/src/api/calls/projects.ts | 6 +- client/src/api/calls/userAllocations.ts | 21 +++- client/src/api/queryKeys/index.ts | 2 + client/src/api/queryKeys/types.ts | 5 + .../ProjectsListItem.module.scss | 22 +++- .../ProjectsListItem/ProjectsListItem.tsx | 23 ++-- .../Projects/ProjectsListItem/types.ts | 1 + .../ProjectsSearchResults.module.scss | 65 ++++++++++ .../ProjectsSearchResults.tsx | 114 ++++++++++++++++++ .../Projects/ProjectsSearchResults/index.tsx | 2 + .../Projects/ProjectsSearchResults/types.ts | 8 ++ .../LayoutTopBar/LayoutTopBar.module.scss | 12 ++ .../Layout/LayoutTopBar/LayoutTopBar.tsx | 8 +- .../LayoutTopBarCalendar.tsx | 4 +- .../ui/TinyLabel/TinyLabel.module.scss | 24 ++++ .../src/components/ui/TinyLabel/TinyLabel.tsx | 15 +++ client/src/components/ui/TinyLabel/index.ts | 2 + client/src/components/ui/TinyLabel/types.ts | 4 + .../helpers/useUserAllocationsAllEpochs.ts | 5 +- .../hooks/queries/useMatchedProjectRewards.ts | 1 + .../src/hooks/queries/useSearchedProjects.ts | 38 +++--- .../queries/useSearchedProjectsDetails.ts | 95 +++++++++++++++ .../src/hooks/queries/useUserAllocations.ts | 5 +- .../src/views/ProjectsView/ProjectsView.tsx | 44 ++++--- client/src/views/ProjectsView/types.ts | 2 +- 25 files changed, 477 insertions(+), 51 deletions(-) create mode 100644 client/src/components/Projects/ProjectsSearchResults/ProjectsSearchResults.module.scss create mode 100644 client/src/components/Projects/ProjectsSearchResults/ProjectsSearchResults.tsx create mode 100644 client/src/components/Projects/ProjectsSearchResults/index.tsx create mode 100644 client/src/components/Projects/ProjectsSearchResults/types.ts create mode 100644 client/src/components/ui/TinyLabel/TinyLabel.module.scss create mode 100644 client/src/components/ui/TinyLabel/TinyLabel.tsx create mode 100644 client/src/components/ui/TinyLabel/index.ts create mode 100644 client/src/components/ui/TinyLabel/types.ts create mode 100644 client/src/hooks/queries/useSearchedProjectsDetails.ts diff --git a/client/src/api/calls/projects.ts b/client/src/api/calls/projects.ts index a2f33077e3..b487f0a873 100644 --- a/client/src/api/calls/projects.ts +++ b/client/src/api/calls/projects.ts @@ -31,7 +31,7 @@ export async function apiGetProjects(epoch: number): Promise { return apiService.get(`${env.serverEndpoint}projects/epoch/${epoch}`).then(({ data }) => data); } -export type ProjectsDetails = { +export type ProjectsSearchResults = { projectsDetails: { address: string; epoch: string; @@ -39,10 +39,10 @@ export type ProjectsDetails = { }[]; }; -export async function apiGetProjectsDetails( +export async function apiGetProjectsSearch( epochs: string, searchPhrases: string, -): Promise { +): Promise { return apiService .get(`${env.serverEndpoint}projects/details?epochs=${epochs}&searchPhrases=${searchPhrases}`) .then(({ data }) => data); diff --git a/client/src/api/calls/userAllocations.ts b/client/src/api/calls/userAllocations.ts index bc33fc6912..7f463588fb 100644 --- a/client/src/api/calls/userAllocations.ts +++ b/client/src/api/calls/userAllocations.ts @@ -1,7 +1,7 @@ import env from 'env'; import apiService from 'services/apiService'; -export type Response = { +export type GetUserAllocationsResponse = { allocations: { address: string; // Funds allocated by user for the project in WEI @@ -10,8 +10,25 @@ export type Response = { isManuallyEdited: boolean | null; }; -export async function apiGetUserAllocations(address: string, epoch: number): Promise { +export async function apiGetUserAllocations( + address: string, + epoch: number, +): Promise { return apiService .get(`${env.serverEndpoint}allocations/user/${address}/epoch/${epoch}`) .then(({ data }) => data); } + +export type AllocationsPerProjectResponse = { + address: string; + amount: string; +}[]; + +export async function apiGetAllocationsPerProject( + projectAddress: string, + epoch: number, +): Promise { + return apiService + .get(`${env.serverEndpoint}allocations/project/${projectAddress}/epoch/${epoch}`) + .then(({ data }) => data); +} diff --git a/client/src/api/queryKeys/index.ts b/client/src/api/queryKeys/index.ts index b832d4713b..b43595ea00 100644 --- a/client/src/api/queryKeys/index.ts +++ b/client/src/api/queryKeys/index.ts @@ -22,6 +22,7 @@ export const ROOTS: Root = { projectsEpoch: 'projectsEpoch', projectsIpfsResults: 'projectsIpfsResults', rewardsRate: 'rewardsRate', + searchResultsDetails: 'searchResultsDetails', upcomingBudget: 'upcomingBudget', uqScore: 'uqScore', userAllocationNonce: 'userAllocationNonce', @@ -69,6 +70,7 @@ export const QUERY_KEYS: QueryKeys = { projectsMetadataPerEpoches: ['projectsMetadataPerEpoches'], rewardsRate: epochNumber => [ROOTS.rewardsRate, epochNumber.toString()], searchResults: ['searchResults'], + searchResultsDetails: (address, epoch) => [ROOTS.searchResultsDetails, address, epoch.toString()], syncStatus: ['syncStatus'], totalAddresses: ['totalAddresses'], totalWithdrawals: ['totalWithdrawals'], diff --git a/client/src/api/queryKeys/types.ts b/client/src/api/queryKeys/types.ts index 590b6de5d2..f5b0ed8d42 100644 --- a/client/src/api/queryKeys/types.ts +++ b/client/src/api/queryKeys/types.ts @@ -22,6 +22,7 @@ export type Root = { projectsEpoch: 'projectsEpoch'; projectsIpfsResults: 'projectsIpfsResults'; rewardsRate: 'rewardsRate'; + searchResultsDetails: 'searchResultsDetails'; upcomingBudget: 'upcomingBudget'; uqScore: 'uqScore'; userAllocationNonce: 'userAllocationNonce'; @@ -70,6 +71,10 @@ export type QueryKeys = { projectsMetadataPerEpoches: ['projectsMetadataPerEpoches']; rewardsRate: (epochNumber: number) => [Root['rewardsRate'], string]; searchResults: ['searchResults']; + searchResultsDetails: ( + address: string, + epoch: number, + ) => [Root['searchResultsDetails'], string, string]; syncStatus: ['syncStatus']; totalAddresses: ['totalAddresses']; totalWithdrawals: ['totalWithdrawals']; diff --git a/client/src/components/Projects/ProjectsListItem/ProjectsListItem.module.scss b/client/src/components/Projects/ProjectsListItem/ProjectsListItem.module.scss index b2d91ab761..105f16bf44 100644 --- a/client/src/components/Projects/ProjectsListItem/ProjectsListItem.module.scss +++ b/client/src/components/Projects/ProjectsListItem/ProjectsListItem.module.scss @@ -34,12 +34,26 @@ right: 0.8rem; } - .imageProfile { - border-radius: 50%; - width: 4rem; - height: 4rem; + .imageProfileWrapper { + position: relative; + + .imageProfile { + border-radius: 50%; + width: 4rem; + height: 4rem; + } + + .tinyLabel { + position: absolute; + top: 0; + right: 0; + white-space: nowrap; + background: $color-white; + text-transform: none; + } } + .body { padding: 0 $projectItemPadding; text-align: left; diff --git a/client/src/components/Projects/ProjectsListItem/ProjectsListItem.tsx b/client/src/components/Projects/ProjectsListItem/ProjectsListItem.tsx index 0431234d4b..e2b1652765 100644 --- a/client/src/components/Projects/ProjectsListItem/ProjectsListItem.tsx +++ b/client/src/components/Projects/ProjectsListItem/ProjectsListItem.tsx @@ -8,6 +8,7 @@ import RewardsWithoutThreshold from 'components/shared/RewardsWithoutThreshold'; import RewardsWithThreshold from 'components/shared/RewardsWithThreshold'; import Description from 'components/ui/Description'; import Img from 'components/ui/Img'; +import TinyLabel from 'components/ui/TinyLabel'; import { WINDOW_PROJECTS_SCROLL_Y } from 'constants/window'; import env from 'env'; import useIdsInAllocation from 'hooks/helpers/useIdsInAllocation'; @@ -26,6 +27,7 @@ const ProjectsListItem: FC = ({ dataTest, epoch, projectIpfsWithRewards, + searchResultsLabel, }) => { const { ipfsGateways } = env; const { address, isLoadingError, profileImageSmall, name, introDescription } = @@ -99,13 +101,20 @@ const ProjectsListItem: FC = ({ ) : (
- `${element}${profileImageSmall}`)} - /> +
+ `${element}${profileImageSmall}`)} + /> + {searchResultsLabel && ( + + )} +
{isAddToAllocateButtonVisible && ( = ({ + orderOption, + projectsIpfsWithRewardsAndEpochs, + isLoading, +}) => { + const { data: currentEpoch } = useCurrentEpoch(); + const { data: isDecisionWindowOpen } = useIsDecisionWindowOpen(); + + const projectsIpfsWithRewardsSorted = + useMemo((): ProjectsSearchResultsProps['projectsIpfsWithRewardsAndEpochs'] => { + switch (orderOption) { + case 'randomized': { + const projectsAddressesRandomizedOrder = JSON.parse( + localStorage.getItem(PROJECTS_ADDRESSES_RANDOMIZED_ORDER)!, + ) as ProjectsAddressesRandomizedOrder; + + const { addressesRandomizedOrder } = projectsAddressesRandomizedOrder; + + return projectsIpfsWithRewardsAndEpochs.sort((a, b) => { + return ( + addressesRandomizedOrder.indexOf(a.address) - + addressesRandomizedOrder.indexOf(b.address) + ); + }); + } + case 'alphabeticalAscending': { + const projectIpfsWithRewardsFiltered = projectsIpfsWithRewardsAndEpochs.filter( + element => element.name !== undefined, + ); + return projectIpfsWithRewardsFiltered.sort((a, b) => a.name!.localeCompare(b.name!)); + } + case 'alphabeticalDescending': { + const projectIpfsWithRewardsFiltered = projectsIpfsWithRewardsAndEpochs.filter( + element => element.name !== undefined, + ); + return projectIpfsWithRewardsFiltered.sort((a, b) => b.name!.localeCompare(a.name!)); + } + case 'donorsAscending': { + return projectsIpfsWithRewardsAndEpochs.sort( + (a, b) => a.numberOfDonors - b.numberOfDonors, + ); + } + case 'donorsDescending': { + return projectsIpfsWithRewardsAndEpochs.sort( + (a, b) => b.numberOfDonors - a.numberOfDonors, + ); + } + case 'totalsAscending': { + const projectIpfsWithRewardsFiltered = projectsIpfsWithRewardsAndEpochs.filter( + element => element.totalValueOfAllocations !== undefined, + ); + return projectIpfsWithRewardsFiltered.sort((a, b) => + Number(a.totalValueOfAllocations! - b.totalValueOfAllocations!), + ); + } + case 'totalsDescending': { + const projectIpfsWithRewardsFiltered = projectsIpfsWithRewardsAndEpochs.filter( + element => element.totalValueOfAllocations !== undefined, + ); + return projectIpfsWithRewardsFiltered.sort((a, b) => + Number(b.totalValueOfAllocations! - a.totalValueOfAllocations!), + ); + } + default: { + return projectsIpfsWithRewardsAndEpochs; + } + } + }, [projectsIpfsWithRewardsAndEpochs, orderOption]); + + return ( +
+ + {projectsIpfsWithRewardsAndEpochs.length > 0 && !isLoading + ? projectsIpfsWithRewardsSorted.map((projectIpfsWithRewards, index) => ( + + )) + : [...Array(5).keys()].map((_, index) => ( + // eslint-disable-next-line react/no-array-index-key + + ))} + +
+ ); +}; + +export default memo(ProjectsSearchResults); diff --git a/client/src/components/Projects/ProjectsSearchResults/index.tsx b/client/src/components/Projects/ProjectsSearchResults/index.tsx new file mode 100644 index 0000000000..de0a42181c --- /dev/null +++ b/client/src/components/Projects/ProjectsSearchResults/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './ProjectsSearchResults'; diff --git a/client/src/components/Projects/ProjectsSearchResults/types.ts b/client/src/components/Projects/ProjectsSearchResults/types.ts new file mode 100644 index 0000000000..ad449f839a --- /dev/null +++ b/client/src/components/Projects/ProjectsSearchResults/types.ts @@ -0,0 +1,8 @@ +import { ProjectsIpfsWithRewardsAndEpochs } from 'hooks/queries/useSearchedProjectsDetails'; +import { OrderOption } from 'views/ProjectsView/types'; + +export default interface ProjectsSearchResultsProps { + isLoading: boolean; + orderOption: OrderOption; + projectsIpfsWithRewardsAndEpochs: ProjectsIpfsWithRewardsAndEpochs[]; +} diff --git a/client/src/components/shared/Layout/LayoutTopBar/LayoutTopBar.module.scss b/client/src/components/shared/Layout/LayoutTopBar/LayoutTopBar.module.scss index bef46570ca..1dec830491 100644 --- a/client/src/components/shared/Layout/LayoutTopBar/LayoutTopBar.module.scss +++ b/client/src/components/shared/Layout/LayoutTopBar/LayoutTopBar.module.scss @@ -23,10 +23,22 @@ } } + .logoWrapper { + position: relative; + } + .octantLogo { cursor: pointer; } + .testnetIndicator { + position: absolute; + top: -1rem; + right: -0.2rem; + text-transform: uppercase; + letter-spacing: 0.1rem; + } + .links { display: flex; margin-left: 2.4rem; diff --git a/client/src/components/shared/Layout/LayoutTopBar/LayoutTopBar.tsx b/client/src/components/shared/Layout/LayoutTopBar/LayoutTopBar.tsx index f282dbdbc6..bc9fb6c20a 100644 --- a/client/src/components/shared/Layout/LayoutTopBar/LayoutTopBar.tsx +++ b/client/src/components/shared/Layout/LayoutTopBar/LayoutTopBar.tsx @@ -11,6 +11,8 @@ import LayoutTopBarCalendar from 'components/shared/Layout/LayoutTopBarCalendar' import Button from 'components/ui/Button'; import Drawer from 'components/ui/Drawer'; import Svg from 'components/ui/Svg'; +import TinyLabel from 'components/ui/TinyLabel'; +import networkConfig from 'constants/networkConfig'; import useIsProjectAdminMode from 'hooks/helpers/useIsProjectAdminMode'; import useMediaQuery from 'hooks/helpers/useMediaQuery'; import useNavigationTabs from 'hooks/helpers/useNavigationTabs'; @@ -127,7 +129,11 @@ const LayoutTopBar: FC = ({ className }) => { return (
- +
+ + {networkConfig.isTestnet || + (true && )} +
{isDesktop && (
{tabs.map(({ label, to, isActive, isDisabled }, index) => ( diff --git a/client/src/components/shared/Layout/LayoutTopBarCalendar/LayoutTopBarCalendar.tsx b/client/src/components/shared/Layout/LayoutTopBarCalendar/LayoutTopBarCalendar.tsx index 1c139e6c75..62f40c1c1c 100644 --- a/client/src/components/shared/Layout/LayoutTopBarCalendar/LayoutTopBarCalendar.tsx +++ b/client/src/components/shared/Layout/LayoutTopBarCalendar/LayoutTopBarCalendar.tsx @@ -1,6 +1,6 @@ import cx from 'classnames'; import { motion, AnimatePresence } from 'framer-motion'; -import React, { ReactNode, useMemo, useState } from 'react'; +import React, { ReactElement, useMemo, useState } from 'react'; import { createPortal } from 'react-dom'; import { useTranslation } from 'react-i18next'; @@ -14,7 +14,7 @@ import { calendar } from 'svg/misc'; import styles from './LayoutTopBarCalendar.module.scss'; -const LayoutTopBarCalendar = (): ReactNode => { +const LayoutTopBarCalendar = (): ReactElement => { const { t } = useTranslation('translation', { keyPrefix: 'layout.topBar' }); const { isMobile } = useMediaQuery(); const { data: isDecisionWindowOpen } = useIsDecisionWindowOpen(); diff --git a/client/src/components/ui/TinyLabel/TinyLabel.module.scss b/client/src/components/ui/TinyLabel/TinyLabel.module.scss new file mode 100644 index 0000000000..cd95e76e8a --- /dev/null +++ b/client/src/components/ui/TinyLabel/TinyLabel.module.scss @@ -0,0 +1,24 @@ +.root { + display: flex; + align-items: center; + justify-content: center; + transform: translate(75%, 0); + background: $color-octant-grey3; + height: 1.6rem; + box-sizing: content-box; + border-radius: 1.2rem; + padding: 0.4rem 0.5rem; + font-size: $font-size-10; + font-weight: $font-weight-bold; +} + +.text { + display: flex; + align-items: center; + color: $color-white; + background: $color-octant-orange; + border-radius: 0.8rem; + height: 1.6rem; + padding: 0 0.8rem; + user-select: none; +} diff --git a/client/src/components/ui/TinyLabel/TinyLabel.tsx b/client/src/components/ui/TinyLabel/TinyLabel.tsx new file mode 100644 index 0000000000..e715193650 --- /dev/null +++ b/client/src/components/ui/TinyLabel/TinyLabel.tsx @@ -0,0 +1,15 @@ +import cx from 'classnames'; +import React, { FC } from 'react'; + +import styles from './TinyLabel.module.scss'; +import TinyLabelProps from './types'; + +const TinyLabel: FC = ({ className, text }) => { + return ( +
+
{text}
+
+ ); +}; + +export default TinyLabel; diff --git a/client/src/components/ui/TinyLabel/index.ts b/client/src/components/ui/TinyLabel/index.ts new file mode 100644 index 0000000000..3739733c96 --- /dev/null +++ b/client/src/components/ui/TinyLabel/index.ts @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './TinyLabel'; diff --git a/client/src/components/ui/TinyLabel/types.ts b/client/src/components/ui/TinyLabel/types.ts new file mode 100644 index 0000000000..2789cbdcea --- /dev/null +++ b/client/src/components/ui/TinyLabel/types.ts @@ -0,0 +1,4 @@ +export default interface TinyLabelProps { + className?: string; + text: string; +} diff --git a/client/src/hooks/helpers/useUserAllocationsAllEpochs.ts b/client/src/hooks/helpers/useUserAllocationsAllEpochs.ts index c9da289bff..3ad4c7dcb1 100644 --- a/client/src/hooks/helpers/useUserAllocationsAllEpochs.ts +++ b/client/src/hooks/helpers/useUserAllocationsAllEpochs.ts @@ -1,7 +1,10 @@ import { useQueries, UseQueryResult } from '@tanstack/react-query'; import { useAccount } from 'wagmi'; -import { apiGetUserAllocations, Response as ApiResponse } from 'api/calls/userAllocations'; +import { + apiGetUserAllocations, + GetUserAllocationsResponse as ApiResponse, +} from 'api/calls/userAllocations'; import { QUERY_KEYS } from 'api/queryKeys'; import useCurrentEpoch from 'hooks/queries/useCurrentEpoch'; import { UserAllocationElement } from 'hooks/queries/useUserAllocations'; diff --git a/client/src/hooks/queries/useMatchedProjectRewards.ts b/client/src/hooks/queries/useMatchedProjectRewards.ts index 17c09a401a..5c2cdcd11e 100644 --- a/client/src/hooks/queries/useMatchedProjectRewards.ts +++ b/client/src/hooks/queries/useMatchedProjectRewards.ts @@ -24,6 +24,7 @@ export type ProjectRewards = { }; function parseResponse(response: Response): ProjectRewards[] { + // TODO unify with totalValueOfAllocations. const totalDonations = response?.rewards.reduce( (acc, { allocated, matched }) => acc + parseUnitsBigInt(allocated, 'wei') + parseUnitsBigInt(matched, 'wei'), diff --git a/client/src/hooks/queries/useSearchedProjects.ts b/client/src/hooks/queries/useSearchedProjects.ts index 4e3dd909ce..369f0c7eea 100644 --- a/client/src/hooks/queries/useSearchedProjects.ts +++ b/client/src/hooks/queries/useSearchedProjects.ts @@ -1,28 +1,38 @@ import { UseQueryResult, useQuery } from '@tanstack/react-query'; -import { apiGetProjectsDetails, ProjectsDetails as ApiProjectsDetails } from 'api/calls/projects'; +import { + apiGetProjectsSearch, + // ProjectsSearchResults as ApiProjectsSearchResults, +} from 'api/calls/projects'; import { QUERY_KEYS } from 'api/queryKeys'; -import { ProjectsDetailsSearchParameters } from 'views/ProjectsView/types'; +import { ProjectsSearchParameters } from 'views/ProjectsView/types'; -type ProjectsDetails = ApiProjectsDetails['projectsDetails']; +export type ProjectsSearchResults = { + address: string; + epoch: number; + name: string; +}[]; export default function useSearchedProjects( - projectsDetailsSearchParameters: ProjectsDetailsSearchParameters | undefined, -): UseQueryResult { + projectsSearchParameters: ProjectsSearchParameters | undefined, +): UseQueryResult { return useQuery({ enabled: - !!projectsDetailsSearchParameters && - ((projectsDetailsSearchParameters.epochs && - projectsDetailsSearchParameters.epochs.length > 0) || - (projectsDetailsSearchParameters.searchPhrases && - projectsDetailsSearchParameters.searchPhrases?.length > 0)), + !!projectsSearchParameters && + ((projectsSearchParameters.epochs && projectsSearchParameters.epochs.length > 0) || + (projectsSearchParameters.searchPhrases && + projectsSearchParameters.searchPhrases?.length > 0)), queryFn: () => - apiGetProjectsDetails( - projectsDetailsSearchParameters!.epochs!.map(String).toString(), - projectsDetailsSearchParameters!.searchPhrases!.join(), + apiGetProjectsSearch( + projectsSearchParameters!.epochs!.map(String).toString(), + projectsSearchParameters!.searchPhrases!.join(), ), // No point in strigifying params, they will just flood the memory. queryKey: QUERY_KEYS.searchResults, - select: data => data.projectsDetails, + select: data => + data.projectsDetails.map(element => ({ + ...element, + epoch: Number(element.epoch), + })), }); } diff --git a/client/src/hooks/queries/useSearchedProjectsDetails.ts b/client/src/hooks/queries/useSearchedProjectsDetails.ts new file mode 100644 index 0000000000..5d7d1a9d6b --- /dev/null +++ b/client/src/hooks/queries/useSearchedProjectsDetails.ts @@ -0,0 +1,95 @@ +import { useQueries } from '@tanstack/react-query'; +import { useCallback } from 'react'; + +import { apiGetProjectIpfsData, apiGetProjects } from 'api/calls/projects'; +import { + apiGetEstimatedMatchedProjectRewards, + apiGetMatchedProjectRewards, +} from 'api/calls/rewards'; +import { apiGetAllocationsPerProject } from 'api/calls/userAllocations'; +import { QUERY_KEYS } from 'api/queryKeys'; +import { parseUnitsBigInt } from 'utils/parseUnitsBigInt'; + +import useCurrentEpoch from './useCurrentEpoch'; +import { ProjectIpfsWithRewards } from './useProjectsIpfsWithRewards'; +import { ProjectsSearchResults } from './useSearchedProjects'; + +export interface ProjectsIpfsWithRewardsAndEpochs extends ProjectIpfsWithRewards { + address: string; + epoch: number; +} + +export interface ProjectsDetails { + data: ProjectsIpfsWithRewardsAndEpochs[]; + isFetching: boolean; + refetch: () => void; +} + +export default function useSearchedProjectsDetails( + projectsSearchResults: ProjectsSearchResults | undefined, +): ProjectsDetails { + const { data: currentEpoch } = useCurrentEpoch(); + + const queries = useQueries({ + queries: (projectsSearchResults || []).map(projectsSearchResult => ({ + queryFn: async () => { + const projectsEpoch = await apiGetProjects(Number(projectsSearchResult.epoch)); + return Promise.all([ + apiGetProjectIpfsData(`${projectsEpoch?.projectsCid}/${projectsSearchResult.address}`), + projectsSearchResult.epoch === currentEpoch + ? apiGetEstimatedMatchedProjectRewards() + : apiGetMatchedProjectRewards(projectsSearchResult.epoch), + apiGetAllocationsPerProject(projectsSearchResult.address, projectsSearchResult.epoch), + projectsSearchResult.epoch, + projectsSearchResult.address, + ]); + }, + queryKey: QUERY_KEYS.searchResultsDetails( + projectsSearchResult.address, + projectsSearchResult.epoch, + ), + retry: false, + })), + }); + + // Trick from https://github.com/TanStack/query/discussions/3364#discussioncomment-2287991. + const refetch = useCallback(() => { + queries.forEach(result => result.refetch()); + }, [queries]); + + const isFetchingQueries = queries.some(({ isFetching }) => isFetching); + + if (isFetchingQueries) { + return { + data: [], + isFetching: isFetchingQueries, + refetch, + }; + } + + return { + data: queries.map(({ data }) => { + const rewards = data && data[1] ? data[1].rewards : []; + const address = data && data[4] ? data[4] : undefined; + const rewardsOfProject = rewards.find(element => element.address === address); + const rewardsOfProjectMatched = rewardsOfProject + ? parseUnitsBigInt(rewardsOfProject.matched, 'wei') + : BigInt(0); + const rewardsOfProjectAllocated = rewardsOfProject + ? parseUnitsBigInt(rewardsOfProject.allocated, 'wei') + : BigInt(0); + + return { + address: data && data[4] ? data[4] : undefined, + donations: rewardsOfProjectAllocated, + epoch: data && data[3] ? data[3] : undefined, + matchedRewards: rewardsOfProjectMatched, + numberOfDonors: data && data[2] ? data[2].length : 0, + totalValueOfAllocations: rewardsOfProjectMatched + rewardsOfProjectAllocated, + ...(data && data[0] ? data[0] : {}), + }; + }), + isFetching: false, + refetch, + }; +} diff --git a/client/src/hooks/queries/useUserAllocations.ts b/client/src/hooks/queries/useUserAllocations.ts index 0d354ab2cd..328f2c169c 100644 --- a/client/src/hooks/queries/useUserAllocations.ts +++ b/client/src/hooks/queries/useUserAllocations.ts @@ -1,7 +1,10 @@ import { UseQueryOptions, UseQueryResult, useQuery } from '@tanstack/react-query'; import { useAccount } from 'wagmi'; -import { apiGetUserAllocations, Response as ApiResponse } from 'api/calls/userAllocations'; +import { + apiGetUserAllocations, + GetUserAllocationsResponse as ApiResponse, +} from 'api/calls/userAllocations'; import { QUERY_KEYS } from 'api/queryKeys'; import { parseUnitsBigInt } from 'utils/parseUnitsBigInt'; diff --git a/client/src/views/ProjectsView/ProjectsView.tsx b/client/src/views/ProjectsView/ProjectsView.tsx index 2e3b989af0..9340f2116f 100644 --- a/client/src/views/ProjectsView/ProjectsView.tsx +++ b/client/src/views/ProjectsView/ProjectsView.tsx @@ -13,6 +13,7 @@ import { useTranslation } from 'react-i18next'; import InfiniteScroll from 'react-infinite-scroller'; import ProjectsList from 'components/Projects/ProjectsList'; +import ProjectsSearchResults from 'components/Projects/ProjectsSearchResults'; import ViewTitle from 'components/shared/ViewTitle/ViewTitle'; import InputSelect from 'components/ui/InputSelect'; import InputText from 'components/ui/InputText'; @@ -26,11 +27,12 @@ import useAreCurrentEpochsProjectsHiddenOutsideAllocationWindow from 'hooks/help import useCurrentEpoch from 'hooks/queries/useCurrentEpoch'; import useIsDecisionWindowOpen from 'hooks/queries/useIsDecisionWindowOpen'; import useSearchedProjects from 'hooks/queries/useSearchedProjects'; +import useSearchedProjectsDetails from 'hooks/queries/useSearchedProjectsDetails'; import { magnifyingGlass } from 'svg/misc'; import { ethAddress as ethAddressRegExp, testRegexp } from 'utils/regExp'; import styles from './ProjectsView.module.scss'; -import { OrderOption, ProjectsDetailsSearchParameters } from './types'; +import { OrderOption, ProjectsSearchParameters } from './types'; import { ORDER_OPTIONS } from './utils'; const ProjectsView = (): ReactElement => { @@ -41,8 +43,8 @@ const ProjectsView = (): ReactElement => { const { data: currentEpoch } = useCurrentEpoch(); const [searchQuery, setSearchQuery] = useState(''); - const [projectsDetailsSearchParameters, setProjectsDetailsSearchParameters] = useState< - ProjectsDetailsSearchParameters | undefined + const [projectsSearchParameters, setProjectsSearchParameters] = useState< + ProjectsSearchParameters | undefined >(undefined); const { data: isDecisionWindowOpen } = useIsDecisionWindowOpen({ @@ -65,22 +67,27 @@ const ProjectsView = (): ReactElement => { return projectsLoadedArchivedEpochsNumber ?? 0; }); - const { data: searchedProjects, refetch: refetchSearchedProjects } = useSearchedProjects( - projectsDetailsSearchParameters, - ); + const { + data: searchedProjects, + refetch: refetchSearchedProjects, + status: statusSearchedProjects, + isFetching: isFetchingSearchedProjects, + } = useSearchedProjects(projectsSearchParameters); + const { + data: searchedProjectsDetails, + refetch: refetchSearchedProjectsDetails, + isFetching: isFetchingSearchedProjectsDetails, + } = useSearchedProjectsDetails(searchedProjects); useEffect(() => { // Refetch is not required when no data already fetched. - if (!searchedProjects || searchedProjects.length === 0) { + if (statusSearchedProjects !== 'success') { return; } refetchSearchedProjects(); + refetchSearchedProjectsDetails(); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [ - searchedProjects, - projectsDetailsSearchParameters?.epochs, - projectsDetailsSearchParameters?.searchPhrases, - ]); + }, [searchedProjects, projectsSearchParameters?.epochs, projectsSearchParameters?.searchPhrases]); const setProjectsDetailsSearchParametersWrapper = (query: string) => { const epochNumbersMatched = [...query.matchAll(testRegexp)]; @@ -110,7 +117,7 @@ const ProjectsView = (): ReactElement => { }); queryFiltered = queryFiltered.trim(); - setProjectsDetailsSearchParameters({ + setProjectsSearchParameters({ epochs: epochNumbers.length > 0 ? epochNumbers @@ -207,7 +214,7 @@ const ProjectsView = (): ReactElement => { variant="underselect" />
- {!areCurrentEpochsProjectsHiddenOutsideAllocationWindow && ( + {searchQuery === '' && !areCurrentEpochsProjectsHiddenOutsideAllocationWindow && ( { orderOption={orderOption} /> )} - {archivedEpochs.length > 0 && ( + {searchQuery !== '' && ( + + )} + {searchQuery === '' && archivedEpochs.length > 0 && ( Date: Fri, 4 Oct 2024 11:19:07 +0200 Subject: [PATCH 192/241] style: extract momoized sorting to useSortedProjects --- client/src/hooks/helpers/useIdsInAllocation/useSortedProjects.ts | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 client/src/hooks/helpers/useIdsInAllocation/useSortedProjects.ts diff --git a/client/src/hooks/helpers/useIdsInAllocation/useSortedProjects.ts b/client/src/hooks/helpers/useIdsInAllocation/useSortedProjects.ts new file mode 100644 index 0000000000..e69de29bb2 From e9c92ea5ca9705b9244f446e2be5099ca1393a9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Zi=C3=B3=C5=82ek?= Date: Fri, 4 Oct 2024 11:20:08 +0200 Subject: [PATCH 193/241] style: extract momoized sorting to useSortedProjects --- .../Projects/ProjectsList/ProjectsList.tsx | 65 ++--------------- .../ProjectsSearchResults.tsx | 69 ++----------------- .../useIdsInAllocation/useSortedProjects.ts | 68 ++++++++++++++++++ 3 files changed, 78 insertions(+), 124 deletions(-) diff --git a/client/src/components/Projects/ProjectsList/ProjectsList.tsx b/client/src/components/Projects/ProjectsList/ProjectsList.tsx index 028b651ce9..eacfcf54d3 100644 --- a/client/src/components/Projects/ProjectsList/ProjectsList.tsx +++ b/client/src/components/Projects/ProjectsList/ProjectsList.tsx @@ -1,17 +1,14 @@ import cx from 'classnames'; -import React, { FC, memo, useMemo } from 'react'; +import React, { FC, memo } from 'react'; import { useTranslation } from 'react-i18next'; import ProjectsListItem from 'components/Projects/ProjectsListItem'; import ProjectsListSkeletonItem from 'components/Projects/ProjectsListSkeletonItem'; import Grid from 'components/shared/Grid'; -import { PROJECTS_ADDRESSES_RANDOMIZED_ORDER } from 'constants/localStorageKeys'; import useEpochDurationLabel from 'hooks/helpers/useEpochDurationLabel'; +import useSortedProjects from 'hooks/helpers/useIdsInAllocation/useSortedProjects'; import useProjectsEpoch from 'hooks/queries/useProjectsEpoch'; -import useProjectsIpfsWithRewards, { - ProjectIpfsWithRewards, -} from 'hooks/queries/useProjectsIpfsWithRewards'; -import { ProjectsAddressesRandomizedOrder } from 'types/localStorage'; +import useProjectsIpfsWithRewards from 'hooks/queries/useProjectsIpfsWithRewards'; import styles from './ProjectsList.module.scss'; import ProjectsListProps from './types'; @@ -33,61 +30,7 @@ const ProjectsList: FC = ({ const isLoading = isFetchingProjectsEpoch || isFetchingProjectsWithRewards; - const projectsIpfsWithRewardsSorted = useMemo((): ProjectIpfsWithRewards[] => { - switch (orderOption) { - case 'randomized': { - const projectsAddressesRandomizedOrder = JSON.parse( - localStorage.getItem(PROJECTS_ADDRESSES_RANDOMIZED_ORDER)!, - ) as ProjectsAddressesRandomizedOrder; - - const { addressesRandomizedOrder } = projectsAddressesRandomizedOrder; - - return projectsIpfsWithRewards.sort((a, b) => { - return ( - addressesRandomizedOrder.indexOf(a.address) - - addressesRandomizedOrder.indexOf(b.address) - ); - }); - } - case 'alphabeticalAscending': { - const projectIpfsWithRewardsFiltered = projectsIpfsWithRewards.filter( - element => element.name !== undefined, - ); - return projectIpfsWithRewardsFiltered.sort((a, b) => a.name!.localeCompare(b.name!)); - } - case 'alphabeticalDescending': { - const projectIpfsWithRewardsFiltered = projectsIpfsWithRewards.filter( - element => element.name !== undefined, - ); - return projectIpfsWithRewardsFiltered.sort((a, b) => b.name!.localeCompare(a.name!)); - } - case 'donorsAscending': { - return projectsIpfsWithRewards.sort((a, b) => a.numberOfDonors - b.numberOfDonors); - } - case 'donorsDescending': { - return projectsIpfsWithRewards.sort((a, b) => b.numberOfDonors - a.numberOfDonors); - } - case 'totalsAscending': { - const projectIpfsWithRewardsFiltered = projectsIpfsWithRewards.filter( - element => element.totalValueOfAllocations !== undefined, - ); - return projectIpfsWithRewardsFiltered.sort((a, b) => - Number(a.totalValueOfAllocations! - b.totalValueOfAllocations!), - ); - } - case 'totalsDescending': { - const projectIpfsWithRewardsFiltered = projectsIpfsWithRewards.filter( - element => element.totalValueOfAllocations !== undefined, - ); - return projectIpfsWithRewardsFiltered.sort((a, b) => - Number(b.totalValueOfAllocations! - a.totalValueOfAllocations!), - ); - } - default: { - return projectsIpfsWithRewards; - } - } - }, [projectsIpfsWithRewards, orderOption]); + const projectsIpfsWithRewardsSorted = useSortedProjects(projectsIpfsWithRewards, orderOption); return (
= ({ const { data: currentEpoch } = useCurrentEpoch(); const { data: isDecisionWindowOpen } = useIsDecisionWindowOpen(); - const projectsIpfsWithRewardsSorted = - useMemo((): ProjectsSearchResultsProps['projectsIpfsWithRewardsAndEpochs'] => { - switch (orderOption) { - case 'randomized': { - const projectsAddressesRandomizedOrder = JSON.parse( - localStorage.getItem(PROJECTS_ADDRESSES_RANDOMIZED_ORDER)!, - ) as ProjectsAddressesRandomizedOrder; - - const { addressesRandomizedOrder } = projectsAddressesRandomizedOrder; - - return projectsIpfsWithRewardsAndEpochs.sort((a, b) => { - return ( - addressesRandomizedOrder.indexOf(a.address) - - addressesRandomizedOrder.indexOf(b.address) - ); - }); - } - case 'alphabeticalAscending': { - const projectIpfsWithRewardsFiltered = projectsIpfsWithRewardsAndEpochs.filter( - element => element.name !== undefined, - ); - return projectIpfsWithRewardsFiltered.sort((a, b) => a.name!.localeCompare(b.name!)); - } - case 'alphabeticalDescending': { - const projectIpfsWithRewardsFiltered = projectsIpfsWithRewardsAndEpochs.filter( - element => element.name !== undefined, - ); - return projectIpfsWithRewardsFiltered.sort((a, b) => b.name!.localeCompare(a.name!)); - } - case 'donorsAscending': { - return projectsIpfsWithRewardsAndEpochs.sort( - (a, b) => a.numberOfDonors - b.numberOfDonors, - ); - } - case 'donorsDescending': { - return projectsIpfsWithRewardsAndEpochs.sort( - (a, b) => b.numberOfDonors - a.numberOfDonors, - ); - } - case 'totalsAscending': { - const projectIpfsWithRewardsFiltered = projectsIpfsWithRewardsAndEpochs.filter( - element => element.totalValueOfAllocations !== undefined, - ); - return projectIpfsWithRewardsFiltered.sort((a, b) => - Number(a.totalValueOfAllocations! - b.totalValueOfAllocations!), - ); - } - case 'totalsDescending': { - const projectIpfsWithRewardsFiltered = projectsIpfsWithRewardsAndEpochs.filter( - element => element.totalValueOfAllocations !== undefined, - ); - return projectIpfsWithRewardsFiltered.sort((a, b) => - Number(b.totalValueOfAllocations! - a.totalValueOfAllocations!), - ); - } - default: { - return projectsIpfsWithRewardsAndEpochs; - } - } - }, [projectsIpfsWithRewardsAndEpochs, orderOption]); + const projectsIpfsWithRewardsSorted = useSortedProjects( + projectsIpfsWithRewardsAndEpochs, + orderOption, + ); return (
diff --git a/client/src/hooks/helpers/useIdsInAllocation/useSortedProjects.ts b/client/src/hooks/helpers/useIdsInAllocation/useSortedProjects.ts index e69de29bb2..aa8deadbaa 100644 --- a/client/src/hooks/helpers/useIdsInAllocation/useSortedProjects.ts +++ b/client/src/hooks/helpers/useIdsInAllocation/useSortedProjects.ts @@ -0,0 +1,68 @@ +import { useMemo } from 'react'; + +import { PROJECTS_ADDRESSES_RANDOMIZED_ORDER } from 'constants/localStorageKeys'; +import { ProjectIpfsWithRewards } from 'hooks/queries/useProjectsIpfsWithRewards'; +import { ProjectsIpfsWithRewardsAndEpochs } from 'hooks/queries/useSearchedProjectsDetails'; +import { ProjectsAddressesRandomizedOrder } from 'types/localStorage'; +import { OrderOption } from 'views/ProjectsView/types'; + +export default function useSortedProjects( + projects: ProjectIpfsWithRewards[] | ProjectsIpfsWithRewardsAndEpochs[], + orderOption: OrderOption, +): ProjectIpfsWithRewards[] | ProjectsIpfsWithRewardsAndEpochs[] { + return useMemo((): ProjectIpfsWithRewards[] | ProjectsIpfsWithRewardsAndEpochs[] => { + switch (orderOption) { + case 'randomized': { + const projectsAddressesRandomizedOrder = JSON.parse( + localStorage.getItem(PROJECTS_ADDRESSES_RANDOMIZED_ORDER)!, + ) as ProjectsAddressesRandomizedOrder; + + const { addressesRandomizedOrder } = projectsAddressesRandomizedOrder; + + return projects.sort((a, b) => { + return ( + addressesRandomizedOrder.indexOf(a.address) - + addressesRandomizedOrder.indexOf(b.address) + ); + }); + } + case 'alphabeticalAscending': { + const projectIpfsWithRewardsFiltered = projects.filter( + element => element.name !== undefined, + ); + return projectIpfsWithRewardsFiltered.sort((a, b) => a.name!.localeCompare(b.name!)); + } + case 'alphabeticalDescending': { + const projectIpfsWithRewardsFiltered = projects.filter( + element => element.name !== undefined, + ); + return projectIpfsWithRewardsFiltered.sort((a, b) => b.name!.localeCompare(a.name!)); + } + case 'donorsAscending': { + return projects.sort((a, b) => a.numberOfDonors - b.numberOfDonors); + } + case 'donorsDescending': { + return projects.sort((a, b) => b.numberOfDonors - a.numberOfDonors); + } + case 'totalsAscending': { + const projectIpfsWithRewardsFiltered = projects.filter( + element => element.totalValueOfAllocations !== undefined, + ); + return projectIpfsWithRewardsFiltered.sort((a, b) => + Number(a.totalValueOfAllocations! - b.totalValueOfAllocations!), + ); + } + case 'totalsDescending': { + const projectIpfsWithRewardsFiltered = projects.filter( + element => element.totalValueOfAllocations !== undefined, + ); + return projectIpfsWithRewardsFiltered.sort((a, b) => + Number(b.totalValueOfAllocations! - a.totalValueOfAllocations!), + ); + } + default: { + return projects; + } + } + }, [projects, orderOption]); +} From 8be761aec59199eeacde72748a7de0cc3a2a798c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Zi=C3=B3=C5=82ek?= Date: Fri, 4 Oct 2024 11:21:19 +0200 Subject: [PATCH 194/241] fix: reverse testnet indicator visibility --- .../components/shared/Layout/LayoutTopBar/LayoutTopBar.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/client/src/components/shared/Layout/LayoutTopBar/LayoutTopBar.tsx b/client/src/components/shared/Layout/LayoutTopBar/LayoutTopBar.tsx index bc9fb6c20a..c8585a7736 100644 --- a/client/src/components/shared/Layout/LayoutTopBar/LayoutTopBar.tsx +++ b/client/src/components/shared/Layout/LayoutTopBar/LayoutTopBar.tsx @@ -131,8 +131,9 @@ const LayoutTopBar: FC = ({ className }) => {
- {networkConfig.isTestnet || - (true && )} + {networkConfig.isTestnet && ( + + )}
{isDesktop && (
From 6a52a6c79a86cbb2e82652c6e38a54d693b3f84d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Zi=C3=B3=C5=82ek?= Date: Fri, 4 Oct 2024 12:04:42 +0200 Subject: [PATCH 195/241] style: code cleanup --- .../ProjectsList/ProjectsList.module.scss | 14 ---- .../ProjectsSearchResults.module.scss | 37 +---------- .../ProjectsSearchResults.tsx | 64 ++++++++++++------- .../src/components/ui/TinyLabel/TinyLabel.tsx | 12 ++-- .../hooks/queries/useMatchedProjectRewards.ts | 2 +- .../src/hooks/queries/useSearchedProjects.ts | 5 +- client/src/locales/en/translation.json | 4 ++ client/src/utils/regExp.test.ts | 56 ++++++++++++++++ client/src/utils/regExp.ts | 2 +- .../src/views/ProjectsView/ProjectsView.tsx | 9 +-- 10 files changed, 115 insertions(+), 90 deletions(-) diff --git a/client/src/components/Projects/ProjectsList/ProjectsList.module.scss b/client/src/components/Projects/ProjectsList/ProjectsList.module.scss index 878d424018..3cfd63fdbb 100644 --- a/client/src/components/Projects/ProjectsList/ProjectsList.module.scss +++ b/client/src/components/Projects/ProjectsList/ProjectsList.module.scss @@ -1,19 +1,5 @@ $elementMargin: 1.6rem; -.noSearchResults { - display: flex; - flex-direction: column; - align-items: center; - flex: 1; - color: $color-octant-grey5; - margin-bottom: 1.6rem; - - .image { - width: 28rem; - margin-bottom: 3.2rem; - } -} - .list { display: flex; flex-wrap: wrap; diff --git a/client/src/components/Projects/ProjectsSearchResults/ProjectsSearchResults.module.scss b/client/src/components/Projects/ProjectsSearchResults/ProjectsSearchResults.module.scss index 878d424018..a0e3eaae79 100644 --- a/client/src/components/Projects/ProjectsSearchResults/ProjectsSearchResults.module.scss +++ b/client/src/components/Projects/ProjectsSearchResults/ProjectsSearchResults.module.scss @@ -6,7 +6,7 @@ $elementMargin: 1.6rem; align-items: center; flex: 1; color: $color-octant-grey5; - margin-bottom: 1.6rem; + margin: 1.6rem 0; .image { width: 28rem; @@ -28,38 +28,3 @@ $elementMargin: 1.6rem; @include flexBasisGutter(2, $elementMargin); } } - -.epochArchive { - width: 100%; - background: $color-white; - height: 6.4rem; - display: flex; - align-items: center; - justify-content: space-between; - padding: 0 2.4rem; - color: $color-octant-dark; - font-size: $font-size-16; - font-weight: $font-weight-bold; - border-radius: $border-radius-16; - margin: 0 0 1.6rem; - - .epochDurationLabel { - color: $color-octant-grey5; - font-size: $font-size-12; - font-weight: $font-weight-medium; - - &.isFetching { - @include skeleton(); - text-indent: 1000%; // moves text outside of view. - white-space: nowrap; - overflow: hidden; - } - } -} - -.divider { - width: 100%; - height: 0.1rem; - background-color: $color-octant-grey1; - margin: 0 0 1.6rem; -} diff --git a/client/src/components/Projects/ProjectsSearchResults/ProjectsSearchResults.tsx b/client/src/components/Projects/ProjectsSearchResults/ProjectsSearchResults.tsx index e319a37bf7..ed1c981edb 100644 --- a/client/src/components/Projects/ProjectsSearchResults/ProjectsSearchResults.tsx +++ b/client/src/components/Projects/ProjectsSearchResults/ProjectsSearchResults.tsx @@ -1,8 +1,10 @@ import React, { FC, memo } from 'react'; +import { useTranslation } from 'react-i18next'; import ProjectsListItem from 'components/Projects/ProjectsListItem'; import ProjectsListSkeletonItem from 'components/Projects/ProjectsListSkeletonItem'; import Grid from 'components/shared/Grid'; +import Img from 'components/ui/Img'; import useSortedProjects from 'hooks/helpers/useIdsInAllocation/useSortedProjects'; import useCurrentEpoch from 'hooks/queries/useCurrentEpoch'; import useIsDecisionWindowOpen from 'hooks/queries/useIsDecisionWindowOpen'; @@ -15,6 +17,10 @@ const ProjectsSearchResults: FC = ({ projectsIpfsWithRewardsAndEpochs, isLoading, }) => { + const { t } = useTranslation('translation', { + keyPrefix: 'components.dedicated.projectsSearchResults', + }); + const { data: currentEpoch } = useCurrentEpoch(); const { data: isDecisionWindowOpen } = useIsDecisionWindowOpen(); @@ -25,30 +31,42 @@ const ProjectsSearchResults: FC = ({ return (
+ {projectsIpfsWithRewardsAndEpochs.length === 0 && !isLoading && ( +
+ + {t('noSearchResults')} +
+ )} - {projectsIpfsWithRewardsAndEpochs.length > 0 && !isLoading - ? projectsIpfsWithRewardsSorted.map((projectIpfsWithRewards, index) => ( - - )) - : [...Array(5).keys()].map((_, index) => ( - // eslint-disable-next-line react/no-array-index-key - - ))} + {projectsIpfsWithRewardsAndEpochs.length > 0 && + !isLoading && + projectsIpfsWithRewardsSorted.map((projectIpfsWithRewards, index) => ( + + ))} + {isLoading && + [...Array(5).keys()].map((_, index) => ( + // eslint-disable-next-line react/no-array-index-key + + ))}
); diff --git a/client/src/components/ui/TinyLabel/TinyLabel.tsx b/client/src/components/ui/TinyLabel/TinyLabel.tsx index e715193650..b396e94442 100644 --- a/client/src/components/ui/TinyLabel/TinyLabel.tsx +++ b/client/src/components/ui/TinyLabel/TinyLabel.tsx @@ -4,12 +4,10 @@ import React, { FC } from 'react'; import styles from './TinyLabel.module.scss'; import TinyLabelProps from './types'; -const TinyLabel: FC = ({ className, text }) => { - return ( -
-
{text}
-
- ); -}; +const TinyLabel: FC = ({ className, text }) => ( +
+
{text}
+
+); export default TinyLabel; diff --git a/client/src/hooks/queries/useMatchedProjectRewards.ts b/client/src/hooks/queries/useMatchedProjectRewards.ts index 5c2cdcd11e..935b8d6e74 100644 --- a/client/src/hooks/queries/useMatchedProjectRewards.ts +++ b/client/src/hooks/queries/useMatchedProjectRewards.ts @@ -24,7 +24,7 @@ export type ProjectRewards = { }; function parseResponse(response: Response): ProjectRewards[] { - // TODO unify with totalValueOfAllocations. + // TODO OCT-2023 unify with totalValueOfAllocations (same thing). const totalDonations = response?.rewards.reduce( (acc, { allocated, matched }) => acc + parseUnitsBigInt(allocated, 'wei') + parseUnitsBigInt(matched, 'wei'), diff --git a/client/src/hooks/queries/useSearchedProjects.ts b/client/src/hooks/queries/useSearchedProjects.ts index 369f0c7eea..0bac36b27b 100644 --- a/client/src/hooks/queries/useSearchedProjects.ts +++ b/client/src/hooks/queries/useSearchedProjects.ts @@ -1,9 +1,6 @@ import { UseQueryResult, useQuery } from '@tanstack/react-query'; -import { - apiGetProjectsSearch, - // ProjectsSearchResults as ApiProjectsSearchResults, -} from 'api/calls/projects'; +import { apiGetProjectsSearch } from 'api/calls/projects'; import { QUERY_KEYS } from 'api/queryKeys'; import { ProjectsSearchParameters } from 'views/ProjectsView/types'; diff --git a/client/src/locales/en/translation.json b/client/src/locales/en/translation.json index 0d5be0030e..fc833b60b7 100644 --- a/client/src/locales/en/translation.json +++ b/client/src/locales/en/translation.json @@ -313,6 +313,10 @@ "projectsList": { "epochArchive": "Epoch {{epoch}} Archive" }, + "projectsSearchResults": { + "searchResultsLabel": "Epoch {{epochNumber}}", + "noSearchResults": "No luck, please try again" + }, "timeCounter": { "hours": "Hours", "minutes": "Minutes", diff --git a/client/src/utils/regExp.test.ts b/client/src/utils/regExp.test.ts index f29d889573..14769be7dc 100644 --- a/client/src/utils/regExp.test.ts +++ b/client/src/utils/regExp.test.ts @@ -8,6 +8,7 @@ import { numbersOnly, percentageOnly, ethAddress, + epochNumberGrabber, } from './regExp'; const regExpTestCases = [ @@ -223,6 +224,8 @@ const regExpTestCases = [ regExp: ethAddress, testCases: [ { expectedValue: true, test: '0xb794f5ea0ba39494ce839613fffba74279579268' }, + { expectedValue: false, test: 'xb794f5ea0ba39494ce839613fffba74279579268' }, + { expectedValue: false, test: '0xb794f5ea0ba39494ce839613fffba7427957926' }, { expectedValue: false, test: '0' }, { expectedValue: false, test: '0,0' }, { expectedValue: false, test: 'abc' }, @@ -247,4 +250,57 @@ describe('regExp', () => { } }); } + + describe('special test cases for epochNumberGrabber', () => { + it('', () => { + expect(JSON.stringify([...'e1'.matchAll(epochNumberGrabber)])).toBe( + JSON.stringify([['e1', '1']]), + ); + expect(JSON.stringify([...'e 1'.matchAll(epochNumberGrabber)])).toBe( + JSON.stringify([['e 1', '1']]), + ); + expect(JSON.stringify([...'e1-2'.matchAll(epochNumberGrabber)])).toBe( + JSON.stringify([['e1-2', '1-2']]), + ); + expect(JSON.stringify([...'e 1-2'.matchAll(epochNumberGrabber)])).toBe( + JSON.stringify([['e 1-2', '1-2']]), + ); + expect(JSON.stringify([...'E1'.matchAll(epochNumberGrabber)])).toBe( + JSON.stringify([['E1', '1']]), + ); + expect(JSON.stringify([...'E 1'.matchAll(epochNumberGrabber)])).toBe( + JSON.stringify([['E 1', '1']]), + ); + expect(JSON.stringify([...'E1-2'.matchAll(epochNumberGrabber)])).toBe( + JSON.stringify([['E1-2', '1-2']]), + ); + expect(JSON.stringify([...'E 1-2'.matchAll(epochNumberGrabber)])).toBe( + JSON.stringify([['E 1-2', '1-2']]), + ); + expect(JSON.stringify([...'Epoch1'.matchAll(epochNumberGrabber)])).toBe( + JSON.stringify([['Epoch1', '1']]), + ); + expect(JSON.stringify([...'Epoch 1'.matchAll(epochNumberGrabber)])).toBe( + JSON.stringify([['Epoch 1', '1']]), + ); + expect(JSON.stringify([...'Epoch1-2'.matchAll(epochNumberGrabber)])).toBe( + JSON.stringify([['Epoch1-2', '1-2']]), + ); + expect(JSON.stringify([...'Epoch 1-2'.matchAll(epochNumberGrabber)])).toBe( + JSON.stringify([['Epoch 1-2', '1-2']]), + ); + expect(JSON.stringify([...'epoch1'.matchAll(epochNumberGrabber)])).toBe( + JSON.stringify([['epoch1', '1']]), + ); + expect(JSON.stringify([...'epoch 1'.matchAll(epochNumberGrabber)])).toBe( + JSON.stringify([['epoch 1', '1']]), + ); + expect(JSON.stringify([...'epoch1-2'.matchAll(epochNumberGrabber)])).toBe( + JSON.stringify([['epoch1-2', '1-2']]), + ); + expect(JSON.stringify([...'epoch 1-2'.matchAll(epochNumberGrabber)])).toBe( + JSON.stringify([['epoch 1-2', '1-2']]), + ); + }); + }); }); diff --git a/client/src/utils/regExp.ts b/client/src/utils/regExp.ts index 345142c1d5..ade7518a35 100644 --- a/client/src/utils/regExp.ts +++ b/client/src/utils/regExp.ts @@ -16,4 +16,4 @@ export const groupingNumbersUpTo3 = /\B(?=(\d{3})+(?!\d))/g; export const ethAddress = /0x[a-fA-F0-9]{40}/g; -export const testRegexp = /(?:^|\s)(?:epoch|Epoch|e|E)(?: ?)([0-9-]+)+/g; +export const epochNumberGrabber = /(?:^|\s)(?:epoch|Epoch|e|E)(?: ?)([0-9-]+)+/g; diff --git a/client/src/views/ProjectsView/ProjectsView.tsx b/client/src/views/ProjectsView/ProjectsView.tsx index 9340f2116f..2cf3607c84 100644 --- a/client/src/views/ProjectsView/ProjectsView.tsx +++ b/client/src/views/ProjectsView/ProjectsView.tsx @@ -29,7 +29,7 @@ import useIsDecisionWindowOpen from 'hooks/queries/useIsDecisionWindowOpen'; import useSearchedProjects from 'hooks/queries/useSearchedProjects'; import useSearchedProjectsDetails from 'hooks/queries/useSearchedProjectsDetails'; import { magnifyingGlass } from 'svg/misc'; -import { ethAddress as ethAddressRegExp, testRegexp } from 'utils/regExp'; +import { ethAddress as ethAddressRegExp, epochNumberGrabber } from 'utils/regExp'; import styles from './ProjectsView.module.scss'; import { OrderOption, ProjectsSearchParameters } from './types'; @@ -84,13 +84,14 @@ const ProjectsView = (): ReactElement => { if (statusSearchedProjects !== 'success') { return; } - refetchSearchedProjects(); - refetchSearchedProjectsDetails(); + refetchSearchedProjects().then(() => { + refetchSearchedProjectsDetails(); + }); // eslint-disable-next-line react-hooks/exhaustive-deps }, [searchedProjects, projectsSearchParameters?.epochs, projectsSearchParameters?.searchPhrases]); const setProjectsDetailsSearchParametersWrapper = (query: string) => { - const epochNumbersMatched = [...query.matchAll(testRegexp)]; + const epochNumbersMatched = [...query.matchAll(epochNumberGrabber)]; const epochNumbers = epochNumbersMatched .map(match => match[1]) From 6be5ddf5dd8e874a2c52e57fdaeef7b7091db43b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Zi=C3=B3=C5=82ek?= Date: Fri, 4 Oct 2024 12:46:47 +0200 Subject: [PATCH 196/241] fix: remove unused CSS --- .../src/components/Projects/ProjectsList/ProjectsList.tsx | 1 - .../ProjectsSearchResults.module.scss | 8 -------- .../ProjectsSearchResults/ProjectsSearchResults.tsx | 1 - 3 files changed, 10 deletions(-) diff --git a/client/src/components/Projects/ProjectsList/ProjectsList.tsx b/client/src/components/Projects/ProjectsList/ProjectsList.tsx index eacfcf54d3..ed20c36b2f 100644 --- a/client/src/components/Projects/ProjectsList/ProjectsList.tsx +++ b/client/src/components/Projects/ProjectsList/ProjectsList.tsx @@ -64,7 +64,6 @@ const ProjectsList: FC = ({ ? projectsIpfsWithRewardsSorted.map((projectIpfsWithRewards, index) => ( = ({ projectsIpfsWithRewardsSorted.map((projectIpfsWithRewards, index) => ( Date: Fri, 4 Oct 2024 14:43:06 +0200 Subject: [PATCH 197/241] style: CR adjustments --- .../ProjectsList/ProjectsList.module.scss | 2 -- .../ProjectsSearchResults.tsx | 3 ++- .../queries/useSearchedProjectsDetails.ts | 12 +++++----- .../src/views/ProjectsView/ProjectsView.tsx | 23 +++++++++++++++++-- 4 files changed, 29 insertions(+), 11 deletions(-) diff --git a/client/src/components/Projects/ProjectsList/ProjectsList.module.scss b/client/src/components/Projects/ProjectsList/ProjectsList.module.scss index 551b3d8f27..8101c16dd5 100644 --- a/client/src/components/Projects/ProjectsList/ProjectsList.module.scss +++ b/client/src/components/Projects/ProjectsList/ProjectsList.module.scss @@ -1,5 +1,3 @@ -$elementMargin: 1.6rem; - .list { display: flex; flex-wrap: wrap; diff --git a/client/src/components/Projects/ProjectsSearchResults/ProjectsSearchResults.tsx b/client/src/components/Projects/ProjectsSearchResults/ProjectsSearchResults.tsx index 282cc447d1..7a3b04a281 100644 --- a/client/src/components/Projects/ProjectsSearchResults/ProjectsSearchResults.tsx +++ b/client/src/components/Projects/ProjectsSearchResults/ProjectsSearchResults.tsx @@ -61,7 +61,8 @@ const ProjectsSearchResults: FC = ({ } /> ))} - {isLoading && + {projectsIpfsWithRewardsAndEpochs.length === 0 && + isLoading && [...Array(5).keys()].map((_, index) => ( // eslint-disable-next-line react/no-array-index-key diff --git a/client/src/hooks/queries/useSearchedProjectsDetails.ts b/client/src/hooks/queries/useSearchedProjectsDetails.ts index 5d7d1a9d6b..96b3208b80 100644 --- a/client/src/hooks/queries/useSearchedProjectsDetails.ts +++ b/client/src/hooks/queries/useSearchedProjectsDetails.ts @@ -69,8 +69,8 @@ export default function useSearchedProjectsDetails( return { data: queries.map(({ data }) => { - const rewards = data && data[1] ? data[1].rewards : []; - const address = data && data[4] ? data[4] : undefined; + const rewards = data?.[1]?.rewards ?? []; + const address = data?.[4]; const rewardsOfProject = rewards.find(element => element.address === address); const rewardsOfProjectMatched = rewardsOfProject ? parseUnitsBigInt(rewardsOfProject.matched, 'wei') @@ -80,13 +80,13 @@ export default function useSearchedProjectsDetails( : BigInt(0); return { - address: data && data[4] ? data[4] : undefined, + address, donations: rewardsOfProjectAllocated, - epoch: data && data[3] ? data[3] : undefined, + epoch: data?.[3], matchedRewards: rewardsOfProjectMatched, - numberOfDonors: data && data[2] ? data[2].length : 0, + numberOfDonors: data?.[2].length ?? 0, totalValueOfAllocations: rewardsOfProjectMatched + rewardsOfProjectAllocated, - ...(data && data[0] ? data[0] : {}), + ...(data?.[0] ?? {}), }; }), isFetching: false, diff --git a/client/src/views/ProjectsView/ProjectsView.tsx b/client/src/views/ProjectsView/ProjectsView.tsx index 2cf3607c84..707402ed49 100644 --- a/client/src/views/ProjectsView/ProjectsView.tsx +++ b/client/src/views/ProjectsView/ProjectsView.tsx @@ -43,6 +43,8 @@ const ProjectsView = (): ReactElement => { const { data: currentEpoch } = useCurrentEpoch(); const [searchQuery, setSearchQuery] = useState(''); + // Helper hook, because actual fetch is called after debounce. Until then, loading state. + const [isProjectsSearchInProgress, setIsProjectsSearchInProgress] = useState(false); const [projectsSearchParameters, setProjectsSearchParameters] = useState< ProjectsSearchParameters | undefined >(undefined); @@ -79,6 +81,13 @@ const ProjectsView = (): ReactElement => { isFetching: isFetchingSearchedProjectsDetails, } = useSearchedProjectsDetails(searchedProjects); + useEffect(() => { + if (isFetchingSearchedProjects || isFetchingSearchedProjectsDetails) { + return; + } + setIsProjectsSearchInProgress(false); + }, [isFetchingSearchedProjects, isFetchingSearchedProjectsDetails]); + useEffect(() => { // Refetch is not required when no data already fetched. if (statusSearchedProjects !== 'success') { @@ -137,6 +146,10 @@ const ProjectsView = (): ReactElement => { const query = e.target.value; setSearchQuery(query); setProjectsDetailsSearchParametersWrapperDebounced(query); + + if (query !== '') { + setIsProjectsSearchInProgress(true); + } }; const lastArchivedEpochNumber = useMemo(() => { @@ -188,7 +201,13 @@ const ProjectsView = (): ReactElement => { return ( <> - {t('viewTitle', { epochNumber: currentEpoch })} + {t('viewTitle', { + epochNumber: + isDecisionWindowOpen || + (!isDecisionWindowOpen && areCurrentEpochsProjectsHiddenOutsideAllocationWindow) + ? currentEpoch! - 1 + : currentEpoch, + })}
{ )} {searchQuery !== '' && ( From e069a7ea0587fad123c483853fd83ea0e14596a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Miko=C5=82ajczyk?= Date: Fri, 4 Oct 2024 16:51:36 +0200 Subject: [PATCH 198/241] oct-2012: allocation empty state --- client/public/images/window-with-dog.webp | Bin 0 -> 47908 bytes .../Allocation/Allocation.module.scss | 45 ++++++++++- .../src/components/Allocation/Allocation.tsx | 73 ++++++++++++++++-- .../AllocationItem/AllocationItem.module.scss | 38 ++++----- .../AllocationItem/AllocationItem.tsx | 6 +- .../shared/Layout/Layout.module.scss | 10 ++- .../LayoutFooter/LayoutFooter.module.scss | 4 +- .../LayoutNavbar/LayoutNavbar.module.scss | 2 +- client/src/locales/en/translation.json | 6 +- client/src/styles/utils/_variables.scss | 2 + 10 files changed, 149 insertions(+), 37 deletions(-) create mode 100644 client/public/images/window-with-dog.webp diff --git a/client/public/images/window-with-dog.webp b/client/public/images/window-with-dog.webp new file mode 100644 index 0000000000000000000000000000000000000000..3ef76a701c5ac194d92fa28f1e6a8b65bc7d2620 GIT binary patch literal 47908 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 literal 0 HcmV?d00001 diff --git a/client/src/components/Allocation/Allocation.module.scss b/client/src/components/Allocation/Allocation.module.scss index 61c91ddf68..38cc9fea3e 100644 --- a/client/src/components/Allocation/Allocation.module.scss +++ b/client/src/components/Allocation/Allocation.module.scss @@ -1,11 +1,10 @@ .root { - padding-bottom: 8.8rem; width: 100%; display: flex; flex-direction: column; + flex: 1; @media #{$tablet-up} { - padding-bottom: 14.4rem; } @media #{$desktop-up} { @@ -43,14 +42,54 @@ .boxesWrapper { width: 100%; + flex: 1; + display: flex; + flex-direction: column; &.withMarginBottom { margin-bottom: 12.8rem; } + &.withPaddingBottom { + padding-bottom: 4rem; + } + @media #{$desktop-up} { - padding: 0 4rem 4rem; + padding: 0 4rem; overflow: auto; } } + + .emptyState { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + flex: 1; + text-align: center; + + .emptyStateImage { + width: 12rem; + + @media #{$tablet-up} { + width: 15rem; + } + } + + .emptyStateText { + display: flex; + align-items: center; + height: 7.2rem; + margin-top: 1.8rem; + color: $color-octant-grey5; + font-size: $font-size-14; + font-weight: $font-weight-semibold; + line-height: 2.2rem; + + @media #{$tablet-up} { + font-size: $font-size-16; + line-height: 2.4rem; + } + } + } } diff --git a/client/src/components/Allocation/Allocation.tsx b/client/src/components/Allocation/Allocation.tsx index cc9f815511..97fa0ac35e 100644 --- a/client/src/components/Allocation/Allocation.tsx +++ b/client/src/components/Allocation/Allocation.tsx @@ -2,9 +2,9 @@ import cx from 'classnames'; import { AnimatePresence } from 'framer-motion'; import debounce from 'lodash/debounce'; import isEmpty from 'lodash/isEmpty'; -import React, { ReactElement, useCallback, useEffect, useRef, useState } from 'react'; +import React, { ReactElement, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { createPortal } from 'react-dom'; -import { useTranslation } from 'react-i18next'; +import { Trans, useTranslation } from 'react-i18next'; import { useAccount } from 'wagmi'; import { SignatureOpType, apiGetPendingMultisigSignatures } from 'api/calls/multisigSignatures'; @@ -14,6 +14,8 @@ import AllocationNavigation from 'components/Allocation/AllocationNavigation'; import AllocationRewardsBox from 'components/Allocation/AllocationRewardsBox'; import AllocationSummary from 'components/Allocation/AllocationSummary'; import ModalAllocationLowUqScore from 'components/Allocation/ModalAllocationLowUqScore'; +import Button from 'components/ui/Button'; +import Img from 'components/ui/Img'; import { DRAWER_TRANSITION_TIME } from 'constants/animations'; import { LAYOUT_NAVBAR_ID } from 'constants/domElementsIds'; import { UQ_SCORE_THRESHOLD_FOR_LEVERAGE_1_BIG_INT } from 'constants/uq'; @@ -37,6 +39,7 @@ import useUqScore from 'hooks/queries/useUqScore'; import useUserAllocationNonce from 'hooks/queries/useUserAllocationNonce'; import useUserAllocations from 'hooks/queries/useUserAllocations'; import useWithdrawals from 'hooks/queries/useWithdrawals'; +import { ROOT_ROUTES } from 'routes/RootRoutes/routes'; import toastService from 'services/toastService'; import useAllocationsStore from 'store/allocations/store'; import { formatUnitsBigInt } from 'utils/formatUnitsBigInt'; @@ -53,7 +56,8 @@ import { const Allocation = (): ReactElement => { const { isConnected } = useAccount(); - const { t } = useTranslation('translation', { keyPrefix: 'components.allocation' }); + const keyPrefix = 'components.allocation'; + const { t } = useTranslation('translation', { keyPrefix }); const [allocationValues, setAllocationValues] = useState([]); const [isManualMode, setIsManualMode] = useState(false); const [addressesWithError, setAddressesWithError] = useState([]); @@ -75,9 +79,11 @@ const Allocation = (): ReactElement => { const { data: isContract } = useIsContract(); const { address: walletAddress } = useAccount(); const [showAllocationNav, setShowAllocationNav] = useState(false); + const [isEmptyStateImageVisible, setIsEmptyStateImageVisible] = useState(true); const navRef = useRef(document.getElementById(LAYOUT_NAVBAR_ID)); const boxesWrapperRef = useRef(null); + const allocationEmptyStateRef = useRef(null); const { data: currentEpoch } = useCurrentEpoch(); const { refetch: refetchHistory } = useHistory(); const { @@ -122,7 +128,7 @@ const Allocation = (): ReactElement => { enabled: isDecisionWindowOpen === true, }, ); - const { isDesktop } = useMediaQuery(); + const { isMobile, isTablet, isDesktop } = useMediaQuery(); const { currentView, @@ -481,6 +487,21 @@ const Allocation = (): ReactElement => { (!!userAllocations?.hasUserAlreadyDoneAllocation && userAllocations.elements.length > 0); const hasUserIndividualReward = !!individualReward && individualReward !== 0n; + const emptyStateI18nKey = useMemo(() => { + if (hasUserIndividualReward && isDecisionWindowOpen) { + if (isMobile) { + return `${keyPrefix}.emptyStateMobileAWOpen`; + } + return `${keyPrefix}.emptyStateAWOpen`; + } + + if (isMobile) { + return `${keyPrefix}.emptyStateMobile`; + } + + return `${keyPrefix}.emptyState`; + }, [hasUserIndividualReward, isDecisionWindowOpen, isMobile]); + const allocationsWithRewards = getAllocationsWithRewards({ allocationValues, areAllocationsAvailableOrAlreadyDone, @@ -548,12 +569,31 @@ const Allocation = (): ReactElement => { navRef.current = document.getElementById(LAYOUT_NAVBAR_ID); }, []); + useEffect(() => { + if (!allocationEmptyStateRef.current || allocations.length) { + return; + } + const { height } = allocationEmptyStateRef.current.getBoundingClientRect(); + + // 200 px (mobile) / 226px (tablet, desktop, large desktop) -> min height of emptyState box to show image + text + if (height < (isMobile ? 200 : 226)) { + setIsEmptyStateImageVisible(false); + return; + } + + setIsEmptyStateImageVisible(true); + }, [allocations.length, isMobile, isTablet]); + return (
{t('allocateRewards')}
{!isEpoch1 && ( { isLocked={currentView === 'summary'} /> )} - {!isEpoch1 && ( + {hasUserIndividualReward && isDecisionWindowOpen && !isEpoch1 && ( { setRewardsForProjectsCallback={onResetAllocationValues} /> )} + {!allocations.length && ( +
+ {isEmptyStateImageVisible && ( + + )} +
+ , + ]} + i18nKey={emptyStateI18nKey} + /> +
+
+ )} + {currentView === 'edit' ? ( areAllocationsAvailableOrAlreadyDone && ( @@ -625,7 +685,6 @@ const Allocation = (): ReactElement => { }} onAllocate={() => onAllocate(true)} /> - {showAllocationNav && (isDesktop ? boxesWrapperRef.current : navRef.current) && createPortal( diff --git a/client/src/components/Allocation/AllocationItem/AllocationItem.module.scss b/client/src/components/Allocation/AllocationItem/AllocationItem.module.scss index 7e6969c191..d056f310cb 100644 --- a/client/src/components/Allocation/AllocationItem/AllocationItem.module.scss +++ b/client/src/components/Allocation/AllocationItem/AllocationItem.module.scss @@ -1,5 +1,5 @@ $removeButtonHeightWidthMobile: 8rem; -$removeButtonHeightWidthDesktop: 10.4rem; +$removeButtonHeightWidth: 10.4rem; .root { width: 100%; @@ -29,18 +29,18 @@ $removeButtonHeightWidthDesktop: 10.4rem; } @media #{$tablet-up} { - height: $removeButtonHeightWidthDesktop; - width: $removeButtonHeightWidthDesktop; + height: $removeButtonHeightWidth; + width: $removeButtonHeightWidth; } } .box { position: relative; z-index: $z-index-2; - padding: 2.4rem; + padding: 1.6rem 1.6rem 1.6rem 2.4rem; - @media #{$tablet-down} { - padding: 1.6rem 1.6rem 1.6rem 2.4rem; + @media #{$tablet-up } { + padding: 2.4rem; } } @@ -63,8 +63,8 @@ $removeButtonHeightWidthDesktop: 10.4rem; .image { margin-right: 2.4rem; border-radius: 50%; - height: 4.8rem; - width: 4.8rem; + height: 5.6rem; + width: 5.6rem; display: none; @media #{$tablet-up} { @@ -78,24 +78,25 @@ $removeButtonHeightWidthDesktop: 10.4rem; .name { min-width: 0; - font-size: $font-size-18; - line-height: 2.1rem; text-align: left; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; - margin: 0 0 0.1rem 0; + font-size: $font-size-14; + margin: 0; + line-height: 1.6rem; - @media #{$tablet-down} { - font-size: $font-size-14; - margin: 0; - line-height: 1.6rem; + @media #{$tablet-up} { + font-size: $font-size-18; + line-height: 2.1rem; + margin: 0 0 0.1rem 0; } } .inputWrapper { flex: 1; - max-width: 26rem; + max-width: 14.2rem; + min-width: 14.2rem; &.isEpoch1 { opacity: 0.5; @@ -105,9 +106,8 @@ $removeButtonHeightWidthDesktop: 10.4rem; animation: horizontal-shaking 0.25s; } - @media #{$tablet-down} { - max-width: 14.2rem; - min-width: 14.2rem; + @media #{$tablet-up} { + max-width: 26rem; } } diff --git a/client/src/components/Allocation/AllocationItem/AllocationItem.tsx b/client/src/components/Allocation/AllocationItem/AllocationItem.tsx index 0bbdff742b..b2f94e6e75 100644 --- a/client/src/components/Allocation/AllocationItem/AllocationItem.tsx +++ b/client/src/components/Allocation/AllocationItem/AllocationItem.tsx @@ -72,7 +72,7 @@ const AllocationItem: FC = ({ const { data: currentEpoch } = useCurrentEpoch(); const { isFetching: isFetchingRewardsThreshold } = useProjectRewardsThreshold(); const { data: isDecisionWindowOpen } = useIsDecisionWindowOpen(); - const { isDesktop } = useMediaQuery(); + const { isMobile } = useMediaQuery(); const [ref, animate] = useAnimate(); const removeButtonRef = useRef(null); const inputRef = useRef(null); @@ -202,7 +202,7 @@ const AllocationItem: FC = ({ const itemHeight = ref.current.getBoundingClientRect().height; setConstraints([(itemHeight + removeButtonLeftPadding) * -1, 0]); - }, [ref, removeButtonRef, isDesktop, isLoading]); + }, [ref, removeButtonRef, isMobile, isLoading]); useEffect(() => { if (isError) { @@ -248,7 +248,7 @@ const AllocationItem: FC = ({ onClick={onRemoveAllocationElement} style={{ scale: removeButtonScaleTransform }} > - + )} height of navbar with AllocationNavigation on mobile + min-height: calc(100% - $layout-top-bar-height - 15.3rem - $layout-navbar-bottom); + + @media #{$tablet-up} { + // 20.1 rem -> height of navbar with AllocationNavigation on tablet + min-height: calc(100% - $layout-top-bar-height - 20.1rem - $layout-navbar-bottom); + } &.isNavigationBottomSuffix { padding-bottom: 20rem; diff --git a/client/src/components/shared/Layout/LayoutFooter/LayoutFooter.module.scss b/client/src/components/shared/Layout/LayoutFooter/LayoutFooter.module.scss index 53221a05ce..3d736baf69 100644 --- a/client/src/components/shared/Layout/LayoutFooter/LayoutFooter.module.scss +++ b/client/src/components/shared/Layout/LayoutFooter/LayoutFooter.module.scss @@ -3,13 +3,13 @@ display: flex; align-items: center; width: 100%; - padding: 4.8rem 0 15.2rem; + padding: 4.8rem 0 21rem; flex-direction: column-reverse; @include layoutMaxWidth(); @media #{$tablet-up} { flex-direction: row; - padding: 4.8rem 0 18.4rem; + padding: 4.8rem 0 26rem; } @media #{$desktop-up} { diff --git a/client/src/components/shared/Layout/LayoutNavbar/LayoutNavbar.module.scss b/client/src/components/shared/Layout/LayoutNavbar/LayoutNavbar.module.scss index 27a8b07c07..2e2aa010e6 100644 --- a/client/src/components/shared/Layout/LayoutNavbar/LayoutNavbar.module.scss +++ b/client/src/components/shared/Layout/LayoutNavbar/LayoutNavbar.module.scss @@ -9,7 +9,7 @@ display: flex; justify-content: center; position: fixed; - bottom: $layoutMarginHorizontal; + bottom: $layout-navbar-bottom; z-index: $z-index-5; pointer-events: none; } diff --git a/client/src/locales/en/translation.json b/client/src/locales/en/translation.json index 47fd245937..77a36b821b 100644 --- a/client/src/locales/en/translation.json +++ b/client/src/locales/en/translation.json @@ -58,6 +58,10 @@ }, "components": { "allocation": { + "emptyState": "Lock some GLM to earn rewards, then add projects here from the
Projects view to donate to them during Allocation window", + "emptyStateAwOpen": "Visit the <0>Projects view to add your favourite projects
so you can donate rewards to them here", + "emptyStateMobile": "Lock some GLM to earn rewards,
then add projects here from the Projects view
to donate to them during Allocation window", + "emptyStateMobileAWOpen": "Visit the <0>Projects view to add your favourite
projects so you can donate rewards to them here", "allocateRewards": "Allocate rewards", "lowUQScoreModal": { "header": "Your UQ score is below 15", @@ -501,4 +505,4 @@ "information": "We're synchronizing things to prepare the
next epoch, so the app will be unavailable
for a little while. Please check back soon." } } -} +} \ No newline at end of file diff --git a/client/src/styles/utils/_variables.scss b/client/src/styles/utils/_variables.scss index 1611ad8df2..82927d765a 100644 --- a/client/src/styles/utils/_variables.scss +++ b/client/src/styles/utils/_variables.scss @@ -42,3 +42,5 @@ $layout-horizontal-padding-large: 11.2rem; $layout-section-width-large-desktop: calc( nth($large-desktop-range, 1) - 2 * $layout-horizontal-padding-large ); +$layout-top-bar-height: 8rem; +$layout-navbar-bottom: 2.4rem; From 8f2d88ded79700e4dbc158144fa8e4dd7c38ed42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Garbaci=C5=84ski?= <57113816+kgarbacinski@users.noreply.github.com> Date: Sat, 5 Oct 2024 12:57:51 +0300 Subject: [PATCH 199/241] OCT-1996: Add a TIMEOUT_LIST for testing (#456) ## Description ## Definition of Done 1. [ ] If required, the desciption of your change is added to the [QA changelog](https://www.notion.so/octantapp/Changelog-for-the-QA-d96fa3b411cf488bb1d8d9a598d88281) 2. [ ] Acceptance criteria are met. 3. [ ] PR is manually tested before the merge by developer(s). - [ ] Happy path is manually checked. 4. [ ] PR is manually tested by QA when their assistance is required (1). - [ ] Octant Areas & Test Cases are checked for impact and updated if required (2). 5. [ ] Unit tests are added unless there is a reason to omit them. 6. [ ] Automated tests are added when required. 7. [ ] The code is merged. 8. [ ] Tech documentation is added / updated, reviewed and approved (including mandatory approval by a code owner, should such exist for changed files). - [ ] BE: Swagger documentation is updated. 9. [ ] When required by QA: - [ ] Deployed to the relevant environment. - [ ] Passed system tests. --- (1) Developer(s) in coordination with QA decide whether it's required. For small tickets introducing small changes QA assistance is most probably not required. (2) [Octant Areas & Test Cases](https://docs.google.com/spreadsheets/d/1cRe6dxuKJV3a4ZskAwWEPvrFkQm6rEfyUCYwLTYw_Cc). --- backend/app/constants.py | 1 + backend/app/modules/modules_factory/current.py | 14 +++++++++++--- backend/app/modules/modules_factory/pending.py | 10 ++++++++-- backend/app/modules/user/antisybil/core.py | 5 ++--- .../app/modules/user/antisybil/service/initial.py | 4 +++- backend/tests/conftest.py | 6 ------ backend/tests/helpers/constants.py | 2 ++ .../modules_factory/test_modules_factory.py | 4 ++-- .../tests/modules/user/antisybil/test_antisybil.py | 14 ++++++++------ 9 files changed, 37 insertions(+), 23 deletions(-) diff --git a/backend/app/constants.py b/backend/app/constants.py index 0ee97bdce7..fc5a2c0817 100644 --- a/backend/app/constants.py +++ b/backend/app/constants.py @@ -558,6 +558,7 @@ } TIMEOUT_LIST = set() +TIMEOUT_LIST_NOT_MAINNET = {"0xdf486eeC7b89C390569194834a2f7A71da05Ee13"} GUEST_LIST_STAMP_PROVIDERS = [ "AllowList#OctantFinal", diff --git a/backend/app/modules/modules_factory/current.py b/backend/app/modules/modules_factory/current.py index 32d0177e9a..4e0ef5d014 100644 --- a/backend/app/modules/modules_factory/current.py +++ b/backend/app/modules/modules_factory/current.py @@ -45,7 +45,12 @@ from app.modules.withdrawals.service.finalized import FinalizedWithdrawals from app.pydantic import Model from app.shared.blockchain_types import compare_blockchain_types, ChainTypes -from app.constants import UQ_THRESHOLD_MAINNET, UQ_THRESHOLD_NOT_MAINNET +from app.constants import ( + UQ_THRESHOLD_MAINNET, + UQ_THRESHOLD_NOT_MAINNET, + TIMEOUT_LIST_NOT_MAINNET, + TIMEOUT_LIST, +) from app.modules.projects.details.service.projects_details import ( StaticProjectsDetailsService, ) @@ -102,7 +107,10 @@ def create(chain_id: int) -> "CurrentServices": user_allocations = SavedUserAllocations() user_allocations_nonce = SavedUserAllocationsNonce() user_withdrawals = FinalizedWithdrawals() - user_antisybil_service = GitcoinPassportAntisybil() + + timeout_list = TIMEOUT_LIST if is_mainnet else TIMEOUT_LIST_NOT_MAINNET + user_antisybil_service = GitcoinPassportAntisybil(timeout_list=timeout_list) + tos_verifier = InitialUserTosVerifier() user_tos = InitialUserTos(verifier=tos_verifier) patron_donations = EventsBasedUserPatronMode() @@ -134,7 +142,7 @@ def create(chain_id: int) -> "CurrentServices": ) uq_threshold = UQ_THRESHOLD_MAINNET if is_mainnet else UQ_THRESHOLD_NOT_MAINNET uniqueness_quotients = PreliminaryUQ( - antisybil=GitcoinPassportAntisybil(), + antisybil=GitcoinPassportAntisybil(timeout_list=timeout_list), budgets=user_budgets, uq_threshold=uq_threshold, ) diff --git a/backend/app/modules/modules_factory/pending.py b/backend/app/modules/modules_factory/pending.py index 2eec837023..6b76c46406 100644 --- a/backend/app/modules/modules_factory/pending.py +++ b/backend/app/modules/modules_factory/pending.py @@ -49,7 +49,12 @@ from app.modules.withdrawals.service.pending import PendingWithdrawals from app.pydantic import Model from app.shared.blockchain_types import compare_blockchain_types, ChainTypes -from app.constants import UQ_THRESHOLD_MAINNET, UQ_THRESHOLD_NOT_MAINNET +from app.constants import ( + UQ_THRESHOLD_MAINNET, + UQ_THRESHOLD_NOT_MAINNET, + TIMEOUT_LIST, + TIMEOUT_LIST_NOT_MAINNET, +) from app.modules.projects.details.service.projects_details import ( StaticProjectsDetailsService, ) @@ -103,8 +108,9 @@ def create(chain_id: int) -> "PendingServices": is_mainnet = compare_blockchain_types(chain_id, ChainTypes.MAINNET) uq_threshold = UQ_THRESHOLD_MAINNET if is_mainnet else UQ_THRESHOLD_NOT_MAINNET + timeout_list = TIMEOUT_LIST if is_mainnet else TIMEOUT_LIST_NOT_MAINNET uniqueness_quotients = PreliminaryUQ( - antisybil=GitcoinPassportAntisybil(), + antisybil=GitcoinPassportAntisybil(timeout_list=timeout_list), budgets=saved_user_budgets, uq_threshold=uq_threshold, ) diff --git a/backend/app/modules/user/antisybil/core.py b/backend/app/modules/user/antisybil/core.py index f4cf044a82..e62a01de78 100644 --- a/backend/app/modules/user/antisybil/core.py +++ b/backend/app/modules/user/antisybil/core.py @@ -3,7 +3,6 @@ from app.modules.user.antisybil.dto import AntisybilStatusDTO from app.constants import ( - TIMEOUT_LIST, GUEST_LIST, GUEST_LIST_STAMP_PROVIDERS, GTC_STAKING_STAMP_PROVIDERS_AND_SCORES, @@ -12,7 +11,7 @@ def determine_antisybil_score( - score: GPStamps, user_address: str + score: GPStamps, user_address: str, timeout_list: set ) -> Optional[AntisybilStatusDTO]: """ Determine the antisybil score for a user. @@ -25,7 +24,7 @@ def determine_antisybil_score( potential_score = _apply_gtc_staking_stamp_nullification(score.score, score) - if user_address in TIMEOUT_LIST: + if user_address in timeout_list: return AntisybilStatusDTO( score=0.0, expires_at=score.expires_at, is_on_timeout_list=True ) diff --git a/backend/app/modules/user/antisybil/service/initial.py b/backend/app/modules/user/antisybil/service/initial.py index 0a25713fae..7fc1f39164 100644 --- a/backend/app/modules/user/antisybil/service/initial.py +++ b/backend/app/modules/user/antisybil/service/initial.py @@ -23,6 +23,8 @@ class GitcoinPassportAntisybil(Model): + timeout_list: set + def get_antisybil_status( self, _: Context, user_address: str ) -> Optional[AntisybilStatusDTO]: @@ -35,7 +37,7 @@ def get_antisybil_status( ) raise ex - return determine_antisybil_score(score, user_address) + return determine_antisybil_score(score, user_address, self.timeout_list) def fetch_antisybil_status( self, _: Context, user_address: str diff --git a/backend/tests/conftest.py b/backend/tests/conftest.py index 3ab1fa5468..285bd962ab 100644 --- a/backend/tests/conftest.py +++ b/backend/tests/conftest.py @@ -1127,12 +1127,6 @@ def patch_gitcoin_passport_fetch_stamps(monkeypatch): ) -@pytest.fixture(scope="function") -def patch_timeout_list(mocker): - timeout_list = ["0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"] - mocker.patch("app.modules.user.antisybil.core.TIMEOUT_LIST", timeout_list) - - @pytest.fixture(scope="function") def patch_etherscan_transactions_api(monkeypatch): monkeypatch.setattr( diff --git a/backend/tests/helpers/constants.py b/backend/tests/helpers/constants.py index cb33e182b2..baf9262d99 100644 --- a/backend/tests/helpers/constants.py +++ b/backend/tests/helpers/constants.py @@ -71,3 +71,5 @@ UQ_THRESHOLD_NOT_MAINNET = 5 UQ_THRESHOLD_MAINNET = 15 + +TIMEOUT_LIST = set() diff --git a/backend/tests/modules/modules_factory/test_modules_factory.py b/backend/tests/modules/modules_factory/test_modules_factory.py index faf3452858..d2439af905 100644 --- a/backend/tests/modules/modules_factory/test_modules_factory.py +++ b/backend/tests/modules/modules_factory/test_modules_factory.py @@ -52,7 +52,7 @@ from app.shared.blockchain_types import ChainTypes from app.modules.user.budgets.service.upcoming import UpcomingUserBudgets from app.modules.snapshots.pending.service.simulated import SimulatedPendingSnapshots -from tests.helpers.constants import UQ_THRESHOLD_MAINNET +from tests.helpers.constants import UQ_THRESHOLD_MAINNET, TIMEOUT_LIST def test_future_services_factory(): @@ -141,7 +141,7 @@ def test_pending_services_factory(): saved_user_budgets = SavedUserBudgets() user_nonce = SavedUserAllocationsNonce() uniqueness_quotients = PreliminaryUQ( - antisybil=GitcoinPassportAntisybil(), + antisybil=GitcoinPassportAntisybil(timeout_list=TIMEOUT_LIST), budgets=saved_user_budgets, uq_threshold=UQ_THRESHOLD_MAINNET, ) diff --git a/backend/tests/modules/user/antisybil/test_antisybil.py b/backend/tests/modules/user/antisybil/test_antisybil.py index 129530e940..cf371ae265 100644 --- a/backend/tests/modules/user/antisybil/test_antisybil.py +++ b/backend/tests/modules/user/antisybil/test_antisybil.py @@ -7,6 +7,7 @@ from app.infrastructure import database from app.modules.common.delegation import get_hashed_addresses from app.modules.user.antisybil.service.initial import GitcoinPassportAntisybil +from tests.helpers.constants import TIMEOUT_LIST from tests.helpers.context import get_context @@ -23,7 +24,7 @@ def test_antisybil_service( ): context = get_context(4) - service = GitcoinPassportAntisybil() + service = GitcoinPassportAntisybil(timeout_list=TIMEOUT_LIST) unknown_address = "0xa0Ee7A142d267C1f36714E4a8F75612F20a79720" try: @@ -53,7 +54,7 @@ def test_gtc_staking_stamp_nullification( mock_users_db, ): context = get_context(4) - service = GitcoinPassportAntisybil() + service = GitcoinPassportAntisybil(timeout_list=TIMEOUT_LIST) _, _, carol = mock_users_db score, expires_at, stamps = service.fetch_antisybil_status(context, carol.address) service.update_antisybil_status(context, carol.address, score, expires_at, stamps) @@ -71,7 +72,7 @@ def test_guest_stamp_score_bump_for_both_gp_and_octant_side_application( ): context = get_context(4) - service = GitcoinPassportAntisybil() + service = GitcoinPassportAntisybil(timeout_list=TIMEOUT_LIST) alice, _, _ = mock_users_db score, expires_at, stamps = service.fetch_antisybil_status(context, alice.address) @@ -108,7 +109,7 @@ def test_antisybil_cant_be_update_when_address_is_delegated(alice, bob): database.score_delegation.save_delegation(primary, secondary, both) db.session.commit() - service = GitcoinPassportAntisybil() + service = GitcoinPassportAntisybil(timeout_list=TIMEOUT_LIST) with pytest.raises(exceptions.AddressAlreadyDelegated): service.update_antisybil_status( @@ -123,12 +124,13 @@ def test_antisybil_score_is_nullified_when_address_on_timeout_list( patch_gitcoin_passport_issue_address_for_scoring, patch_gitcoin_passport_fetch_score, patch_gitcoin_passport_fetch_stamps, - patch_timeout_list, mock_users_db, ): context = get_context(4) - service = GitcoinPassportAntisybil() + service = GitcoinPassportAntisybil( + timeout_list={"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"} + ) alice, _, _ = mock_users_db timeout_address = alice.address score, expires_at, stamps = service.fetch_antisybil_status(context, timeout_address) From c2ac9e039b8c0d405d0e392a0dd4bda005002ac6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Zi=C3=B3=C5=82ek?= Date: Sat, 5 Oct 2024 22:30:39 +0200 Subject: [PATCH 200/241] feat: modal initial prototype --- client/public/images/sybil.webp | Bin 0 -> 6914 bytes client/src/App.tsx | 7 ++ .../ModalAllocationLowUqScore.tsx | 3 +- .../ModalSuspectedSybil.module.scss | 51 +++++++++++++ .../ModalSuspectedSybil.tsx | 68 ++++++++++++++++++ .../shared/ModalSuspectedSybil/index.tsx | 2 + .../shared/ModalSuspectedSybil/types.ts | 5 ++ client/src/constants/urls.ts | 1 + client/src/locales/en/translation.json | 7 ++ client/src/routes/RootRoutes/RootRoutes.tsx | 4 +- 10 files changed, 144 insertions(+), 4 deletions(-) create mode 100644 client/public/images/sybil.webp create mode 100644 client/src/components/shared/ModalSuspectedSybil/ModalSuspectedSybil.module.scss create mode 100644 client/src/components/shared/ModalSuspectedSybil/ModalSuspectedSybil.tsx create mode 100644 client/src/components/shared/ModalSuspectedSybil/index.tsx create mode 100644 client/src/components/shared/ModalSuspectedSybil/types.ts diff --git a/client/public/images/sybil.webp b/client/public/images/sybil.webp new file mode 100644 index 0000000000000000000000000000000000000000..372837afce9a1514c768dac758b042ec13ec6701 GIT binary patch literal 6914 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 IzyJUM0E7#tEdT%j literal 0 HcmV?d00001 diff --git a/client/src/App.tsx b/client/src/App.tsx index e78b2da3ea..a2cfe3018b 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -5,6 +5,7 @@ import { useAccount } from 'wagmi'; import AppLoader from 'components/shared/AppLoader'; import Layout from 'components/shared/Layout'; import ModalOnboarding from 'components/shared/ModalOnboarding/ModalOnboarding'; +import ModalSuspectedSybil from 'components/shared/ModalSuspectedSybil'; import OnboardingStepper from 'components/shared/OnboardingStepper'; import useAppConnectManager from 'hooks/helpers/useAppConnectManager'; import useAppIsLoading from 'hooks/helpers/useAppIsLoading'; @@ -49,6 +50,12 @@ const App = (): ReactElement => { {!isSyncingInProgress && !isProjectAdminMode && } + {}, + }} + /> {isConnected && !isOnboardingDone && !isOnboardingModalOpen && } diff --git a/client/src/components/Allocation/ModalAllocationLowUqScore/ModalAllocationLowUqScore.tsx b/client/src/components/Allocation/ModalAllocationLowUqScore/ModalAllocationLowUqScore.tsx index fbd72159a7..9477c4439c 100644 --- a/client/src/components/Allocation/ModalAllocationLowUqScore/ModalAllocationLowUqScore.tsx +++ b/client/src/components/Allocation/ModalAllocationLowUqScore/ModalAllocationLowUqScore.tsx @@ -1,14 +1,13 @@ import React, { FC } from 'react'; import { useTranslation } from 'react-i18next'; +import AllocationLowUqScore from 'components/Allocation/AllocationLowUqScore'; import Img from 'components/ui/Img'; import Modal from 'components/ui/Modal'; import styles from './ModalAllocationLowUqScore.module.scss'; import ModalAllocationLowUqScoreProps from './types'; -import AllocationLowUqScore from '../AllocationLowUqScore'; - const ModalAllocationLowUqScore: FC = ({ modalProps, onAllocate, diff --git a/client/src/components/shared/ModalSuspectedSybil/ModalSuspectedSybil.module.scss b/client/src/components/shared/ModalSuspectedSybil/ModalSuspectedSybil.module.scss new file mode 100644 index 0000000000..649c924360 --- /dev/null +++ b/client/src/components/shared/ModalSuspectedSybil/ModalSuspectedSybil.module.scss @@ -0,0 +1,51 @@ +.image { + width: 22rem; + height: 11rem; + margin: 3.5rem auto; + + @media #{$desktop-up} { + margin: 3.8rem auto; + width: 28rem; + height: 14rem; + } +} + +.text { + color: $color-octant-grey5; + margin-right: 0.8rem; +} + +.box { + height: 8rem; + padding: 0 3.6rem 0 2.4rem; + margin: 3.2rem 0; + + .checkbox { + width: 2.4rem; + height: 2.4rem; + } + + .checkboxLabel { + font-size: $font-size-16; + text-align: left; + margin-left: 2.6rem; + font-weight: $font-weight-semibold; + } +} + +.buttonsContainer { + width: 100%; + display: flex; + justify-content: space-between; + + .button { + flex: 1; + cursor: pointer; + padding: 0; + height: 4.8rem; + + &:first-child { + margin-right: 1.6rem; + } + } +} diff --git a/client/src/components/shared/ModalSuspectedSybil/ModalSuspectedSybil.tsx b/client/src/components/shared/ModalSuspectedSybil/ModalSuspectedSybil.tsx new file mode 100644 index 0000000000..35e884b1f2 --- /dev/null +++ b/client/src/components/shared/ModalSuspectedSybil/ModalSuspectedSybil.tsx @@ -0,0 +1,68 @@ +import React, { FC, useState } from 'react'; +import { Trans, useTranslation } from 'react-i18next'; + +import BoxRounded from 'components/ui/BoxRounded'; +import Button from 'components/ui/Button/Button'; +import Img from 'components/ui/Img'; +import InputCheckbox from 'components/ui/InputCheckbox'; +import Modal from 'components/ui/Modal'; +import { TIME_OUT_LIST_DISPUTE_FORM } from 'constants/urls'; + +import styles from './ModalSuspectedSybil.module.scss'; +import ModalAllocationLowUqScoreProps from './types'; + +const ModalSuspectedSybil: FC = ({ modalProps }) => { + const { t } = useTranslation('translation', { + keyPrefix: 'views.onboarding.suspectedSybilModal', + }); + + const [isChecked, setIsChecked] = useState(false); + + return ( + } + isOverflowOnClickDisabled + showCloseButton={false} + {...modalProps} + isOpen + > +
+ , +
+ + setIsChecked(prev => !prev)} + size="big" + /> + + +
+ + +
+
+ ); +}; + +export default ModalSuspectedSybil; diff --git a/client/src/components/shared/ModalSuspectedSybil/index.tsx b/client/src/components/shared/ModalSuspectedSybil/index.tsx new file mode 100644 index 0000000000..03b0442579 --- /dev/null +++ b/client/src/components/shared/ModalSuspectedSybil/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './ModalSuspectedSybil'; diff --git a/client/src/components/shared/ModalSuspectedSybil/types.ts b/client/src/components/shared/ModalSuspectedSybil/types.ts new file mode 100644 index 0000000000..8d736bb2c0 --- /dev/null +++ b/client/src/components/shared/ModalSuspectedSybil/types.ts @@ -0,0 +1,5 @@ +import ModalProps from 'components/ui/Modal/types'; + +export default interface ModalAllocationLowUqScoreProps { + modalProps: Omit; +} diff --git a/client/src/constants/urls.ts b/client/src/constants/urls.ts index 22362f3872..f7237a58a0 100644 --- a/client/src/constants/urls.ts +++ b/client/src/constants/urls.ts @@ -12,3 +12,4 @@ export const GITCOIN_PASSPORT = 'https://support.passport.xyz/passport-knowledge export const GITCOIN_PASSPORT_CUSTOM_OCTANT_DASHBOARD = 'https://passport.gitcoin.co/#/octant/'; export const SCORING_20_FOR_HUMANS_GUIDE = 'https://support.passport.xyz/passport-knowledge-base/stamps/scoring-20-for-humans'; +export const TIME_OUT_LIST_DISPUTE_FORM = 'https://octant.fillout.com/t/wLNsbSGJKWus'; diff --git a/client/src/locales/en/translation.json b/client/src/locales/en/translation.json index fb1f25c94c..f625ce331b 100644 --- a/client/src/locales/en/translation.json +++ b/client/src/locales/en/translation.json @@ -422,6 +422,13 @@ "projectCosts": "Project costs" }, "onboarding": { + "suspectedSybilModal": { + "header": "This account looks like a sybil", + "text": "We have flagged this account as a <0>sybil which means your allocation will receive no match funding this epoch. If you would like to dispute this, please <1>fill out this form & we’ll review it.", + "checkboxLabel": "I understand that I will receive \u2028no match funding this epoch", + "goToDisputeForm": "Go to dispute form", + "close": "Close" + }, "stepsCommon": { "usingTheApp": { "header": "Using the app", diff --git a/client/src/routes/RootRoutes/RootRoutes.tsx b/client/src/routes/RootRoutes/RootRoutes.tsx index aaff0a0af9..d9804fd680 100644 --- a/client/src/routes/RootRoutes/RootRoutes.tsx +++ b/client/src/routes/RootRoutes/RootRoutes.tsx @@ -1,4 +1,4 @@ -import React, { ReactNode, useEffect, useRef } from 'react'; +import React, { ReactElement, useEffect, useRef } from 'react'; import { Navigate, Route, Routes, useLocation } from 'react-router-dom'; import env from 'env'; @@ -17,7 +17,7 @@ import SettingsView from 'views/SettingsView'; import { ROOT_ROUTES } from './routes'; -const RootRoutes = (): ReactNode => { +const RootRoutes = (): ReactElement => { const { isDesktop } = useMediaQuery(); const { data: currentEpoch } = useCurrentEpoch(); const isPreLaunch = getIsPreLaunch(currentEpoch); From f644b8d2363e77683f84ca6d48179e2f5bd79adc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Zi=C3=B3=C5=82ek?= Date: Sat, 5 Oct 2024 22:37:22 +0200 Subject: [PATCH 201/241] style: renames --- client/src/App.tsx | 4 ++-- .../components/shared/ModalSuspectedSybil/index.tsx | 2 -- .../ModalTimeoutListPresence.module.scss} | 0 .../ModalTimeoutListPresence.tsx} | 10 +++++----- .../shared/ModalTimeoutListPresence/index.tsx | 2 ++ .../types.ts | 2 +- client/src/constants/localStorageKeys.ts | 9 +++++---- 7 files changed, 15 insertions(+), 14 deletions(-) delete mode 100644 client/src/components/shared/ModalSuspectedSybil/index.tsx rename client/src/components/shared/{ModalSuspectedSybil/ModalSuspectedSybil.module.scss => ModalTimeoutListPresence/ModalTimeoutListPresence.module.scss} (100%) rename client/src/components/shared/{ModalSuspectedSybil/ModalSuspectedSybil.tsx => ModalTimeoutListPresence/ModalTimeoutListPresence.tsx} (87%) create mode 100644 client/src/components/shared/ModalTimeoutListPresence/index.tsx rename client/src/components/shared/{ModalSuspectedSybil => ModalTimeoutListPresence}/types.ts (65%) diff --git a/client/src/App.tsx b/client/src/App.tsx index a2cfe3018b..5edff5d9bc 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -5,7 +5,7 @@ import { useAccount } from 'wagmi'; import AppLoader from 'components/shared/AppLoader'; import Layout from 'components/shared/Layout'; import ModalOnboarding from 'components/shared/ModalOnboarding/ModalOnboarding'; -import ModalSuspectedSybil from 'components/shared/ModalSuspectedSybil'; +import ModalTimeoutListPresence from 'components/shared/ModalTimeoutListPresence'; import OnboardingStepper from 'components/shared/OnboardingStepper'; import useAppConnectManager from 'hooks/helpers/useAppConnectManager'; import useAppIsLoading from 'hooks/helpers/useAppIsLoading'; @@ -50,7 +50,7 @@ const App = (): ReactElement => { {!isSyncingInProgress && !isProjectAdminMode && } - {}, diff --git a/client/src/components/shared/ModalSuspectedSybil/index.tsx b/client/src/components/shared/ModalSuspectedSybil/index.tsx deleted file mode 100644 index 03b0442579..0000000000 --- a/client/src/components/shared/ModalSuspectedSybil/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line no-restricted-exports -export { default } from './ModalSuspectedSybil'; diff --git a/client/src/components/shared/ModalSuspectedSybil/ModalSuspectedSybil.module.scss b/client/src/components/shared/ModalTimeoutListPresence/ModalTimeoutListPresence.module.scss similarity index 100% rename from client/src/components/shared/ModalSuspectedSybil/ModalSuspectedSybil.module.scss rename to client/src/components/shared/ModalTimeoutListPresence/ModalTimeoutListPresence.module.scss diff --git a/client/src/components/shared/ModalSuspectedSybil/ModalSuspectedSybil.tsx b/client/src/components/shared/ModalTimeoutListPresence/ModalTimeoutListPresence.tsx similarity index 87% rename from client/src/components/shared/ModalSuspectedSybil/ModalSuspectedSybil.tsx rename to client/src/components/shared/ModalTimeoutListPresence/ModalTimeoutListPresence.tsx index 35e884b1f2..1c4f6eac81 100644 --- a/client/src/components/shared/ModalSuspectedSybil/ModalSuspectedSybil.tsx +++ b/client/src/components/shared/ModalTimeoutListPresence/ModalTimeoutListPresence.tsx @@ -8,10 +8,10 @@ import InputCheckbox from 'components/ui/InputCheckbox'; import Modal from 'components/ui/Modal'; import { TIME_OUT_LIST_DISPUTE_FORM } from 'constants/urls'; -import styles from './ModalSuspectedSybil.module.scss'; -import ModalAllocationLowUqScoreProps from './types'; +import styles from './ModalTimeoutListPresence.module.scss'; +import ModalTimeoutListPresenceProps from './types'; -const ModalSuspectedSybil: FC = ({ modalProps }) => { +const ModalTimeoutListPresence: FC = ({ modalProps }) => { const { t } = useTranslation('translation', { keyPrefix: 'views.onboarding.suspectedSybilModal', }); @@ -21,7 +21,7 @@ const ModalSuspectedSybil: FC = ({ modalProps }) return ( } isOverflowOnClickDisabled @@ -65,4 +65,4 @@ const ModalSuspectedSybil: FC = ({ modalProps }) ); }; -export default ModalSuspectedSybil; +export default ModalTimeoutListPresence; diff --git a/client/src/components/shared/ModalTimeoutListPresence/index.tsx b/client/src/components/shared/ModalTimeoutListPresence/index.tsx new file mode 100644 index 0000000000..768d14b2da --- /dev/null +++ b/client/src/components/shared/ModalTimeoutListPresence/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './ModalTimeoutListPresence'; diff --git a/client/src/components/shared/ModalSuspectedSybil/types.ts b/client/src/components/shared/ModalTimeoutListPresence/types.ts similarity index 65% rename from client/src/components/shared/ModalSuspectedSybil/types.ts rename to client/src/components/shared/ModalTimeoutListPresence/types.ts index 8d736bb2c0..1e265762fb 100644 --- a/client/src/components/shared/ModalSuspectedSybil/types.ts +++ b/client/src/components/shared/ModalTimeoutListPresence/types.ts @@ -1,5 +1,5 @@ import ModalProps from 'components/ui/Modal/types'; -export default interface ModalAllocationLowUqScoreProps { +export default interface ModalTimeoutListPresenceProps { modalProps: Omit; } diff --git a/client/src/constants/localStorageKeys.ts b/client/src/constants/localStorageKeys.ts index 8c1689390d..06263deacb 100644 --- a/client/src/constants/localStorageKeys.ts +++ b/client/src/constants/localStorageKeys.ts @@ -41,10 +41,6 @@ export const IS_CRYPTO_MAIN_VALUE_DISPLAY = getLocalStorageKey( // Delegation const delegationPrefix = 'delegation'; -export const IS_DELEGATION_IN_PROGRESS = getLocalStorageKey( - delegationPrefix, - 'isDelegationInProgress', -); export const IS_DELEGATION_COMPLETED = getLocalStorageKey( delegationPrefix, @@ -67,3 +63,8 @@ export const SECONDARY_ADDRESS_SCORE = getLocalStorageKey( delegationPrefix, 'secondaryAddressScore', ); + +export const TIMEOUT_LIST_PRESENCE_MODAL_CLOSED = getLocalStorageKey( + delegationPrefix, + 'timeoutListPresenceModalClosed', +); From 50684f125a37acd866df504c198a6967c1fcebee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Zi=C3=B3=C5=82ek?= Date: Sun, 6 Oct 2024 00:22:36 +0200 Subject: [PATCH 202/241] feat: rest of the UI --- client/src/App.tsx | 7 +- client/src/api/calls/antisybilStatus.ts | 1 + .../Home/HomeGridUQScore/HomeGridUQScore.tsx | 6 +- .../CalculatingUQScore/CalculatingUQScore.tsx | 10 +-- .../RecalculatingScore/RecalculatingScore.tsx | 8 +- .../ModalOnboarding/ModalOnboarding.tsx | 24 +++++- .../ModalTimeoutListPresence.tsx | 27 ++++--- .../shared/ModalTimeoutListPresence/types.ts | 5 -- client/src/constants/localStorageKeys.ts | 4 +- client/src/hooks/helpers/useAppIsLoading.ts | 10 ++- .../hooks/queries/useAntisybilStatusScore.ts | 13 +++- client/src/locales/en/translation.json | 2 +- .../src/services/localStorageService.test.ts | 76 +++++++++++++++++++ client/src/services/localStorageService.ts | 19 +++++ client/src/store/delegation/store.ts | 10 +++ client/src/store/delegation/types.ts | 9 +++ 16 files changed, 192 insertions(+), 39 deletions(-) delete mode 100644 client/src/components/shared/ModalTimeoutListPresence/types.ts diff --git a/client/src/App.tsx b/client/src/App.tsx index 5edff5d9bc..2fd5bbb4de 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -50,12 +50,7 @@ const App = (): ReactElement => { {!isSyncingInProgress && !isProjectAdminMode && } - {}, - }} - /> + {isConnected && !isOnboardingDone && !isOnboardingModalOpen && } diff --git a/client/src/api/calls/antisybilStatus.ts b/client/src/api/calls/antisybilStatus.ts index 75a8d5b209..50e9af5a7b 100644 --- a/client/src/api/calls/antisybilStatus.ts +++ b/client/src/api/calls/antisybilStatus.ts @@ -3,6 +3,7 @@ import apiService from 'services/apiService'; export type Response = { expires_at: string; + isOnTimeOutList: boolean; score: string; status: string; }; diff --git a/client/src/components/Home/HomeGridUQScore/HomeGridUQScore.tsx b/client/src/components/Home/HomeGridUQScore/HomeGridUQScore.tsx index bf8fecf6d3..8ea9ed7343 100644 --- a/client/src/components/Home/HomeGridUQScore/HomeGridUQScore.tsx +++ b/client/src/components/Home/HomeGridUQScore/HomeGridUQScore.tsx @@ -184,16 +184,16 @@ const HomeGridUQScore: FC = ({ className }) => { return; } if (isDelegationCompleted) { - setSecondaryAddressScore(antisybilStatusScore); + setSecondaryAddressScore(antisybilStatusScore?.score); } else { if (refreshAntisybilStatusError) { setDelegationPrimaryAddress(address); setDelegationSecondaryAddress('0x???'); } setPrimaryAddressScore( - antisybilStatusScore < UQ_SCORE_THRESHOLD_FOR_LEVERAGE_1 && uqScore === 100n + antisybilStatusScore?.score < UQ_SCORE_THRESHOLD_FOR_LEVERAGE_1 && uqScore === 100n ? UQ_SCORE_THRESHOLD_FOR_LEVERAGE_1 - : antisybilStatusScore, + : antisybilStatusScore?.score, ); } setIsFetchingScore(false); diff --git a/client/src/components/Home/HomeGridUQScore/ModalCalculatingUQScore/CalculatingUQScore/CalculatingUQScore.tsx b/client/src/components/Home/HomeGridUQScore/ModalCalculatingUQScore/CalculatingUQScore/CalculatingUQScore.tsx index 752b3c8cf3..62cab171ba 100644 --- a/client/src/components/Home/HomeGridUQScore/ModalCalculatingUQScore/CalculatingUQScore/CalculatingUQScore.tsx +++ b/client/src/components/Home/HomeGridUQScore/ModalCalculatingUQScore/CalculatingUQScore/CalculatingUQScore.tsx @@ -69,13 +69,13 @@ const CalculatingUQScore: FC = ({ setShowCloseButton }) const showLowScoreInfo = isScoreHighlighted && secondaryAddressAntisybilStatusScore !== undefined && - secondaryAddressAntisybilStatusScore < UQ_SCORE_THRESHOLD_FOR_LEVERAGE_1; + secondaryAddressAntisybilStatusScore.score < UQ_SCORE_THRESHOLD_FOR_LEVERAGE_1; const scoreHighlight = useMemo(() => { if (!isScoreHighlighted || secondaryAddressAntisybilStatusScore === undefined) { return undefined; } - if (secondaryAddressAntisybilStatusScore < UQ_SCORE_THRESHOLD_FOR_LEVERAGE_1) { + if (secondaryAddressAntisybilStatusScore.score < UQ_SCORE_THRESHOLD_FOR_LEVERAGE_1) { return 'red'; } return 'black'; @@ -146,12 +146,12 @@ const CalculatingUQScore: FC = ({ setShowCloseButton }) setLastDoneStep(1); setTimeout(() => { setLastDoneStep(2); - if (secondaryAddressAntisybilStatusScore < UQ_SCORE_THRESHOLD_FOR_LEVERAGE_1) { + if (secondaryAddressAntisybilStatusScore.score < UQ_SCORE_THRESHOLD_FOR_LEVERAGE_1) { setShowCloseButton(true); setIsDelegationInProgress(false); return; } - setSecondaryAddressScore(secondaryAddressAntisybilStatusScore); + setSecondaryAddressScore(secondaryAddressAntisybilStatusScore.score); setCalculatingUQScoreMode('sign'); }, 2500); }, 2500); @@ -185,7 +185,7 @@ const CalculatingUQScore: FC = ({ setShowCloseButton }) } mode={calculatingUQScoreMode} onSignMessage={() => signMessageAndDelegate(true)} - score={secondaryAddressAntisybilStatusScore ?? 0} + score={secondaryAddressAntisybilStatusScore?.score ?? 0} scoreHighlight={scoreHighlight} showActiveDot={calculatingUQScoreMode === 'sign'} /> diff --git a/client/src/components/Home/HomeGridUQScore/ModalRecalculatingScore/RecalculatingScore/RecalculatingScore.tsx b/client/src/components/Home/HomeGridUQScore/ModalRecalculatingScore/RecalculatingScore/RecalculatingScore.tsx index 77e5b129a5..c1954de503 100644 --- a/client/src/components/Home/HomeGridUQScore/ModalRecalculatingScore/RecalculatingScore/RecalculatingScore.tsx +++ b/client/src/components/Home/HomeGridUQScore/ModalRecalculatingScore/RecalculatingScore/RecalculatingScore.tsx @@ -56,10 +56,14 @@ const RecalculatingScore: FC = ({ onLastStepDone }) => ) { return 0; } - if (!isDelegationCompleted && antisybilStatusScore < DELEGATION_MIN_SCORE && uqScore === 100n) { + if ( + !isDelegationCompleted && + antisybilStatusScore.score < DELEGATION_MIN_SCORE && + uqScore === 100n + ) { return DELEGATION_MIN_SCORE; } - return antisybilStatusScore; + return antisybilStatusScore.score; }, [antisybilStatusScore, uqScore, lastDoneStep, isDelegationCompleted, isErrorUqScore]); const scoreHighlight = lastDoneStep && lastDoneStep >= 1 ? 'black' : undefined; diff --git a/client/src/components/shared/ModalOnboarding/ModalOnboarding.tsx b/client/src/components/shared/ModalOnboarding/ModalOnboarding.tsx index 195c36f710..f793ee007b 100644 --- a/client/src/components/shared/ModalOnboarding/ModalOnboarding.tsx +++ b/client/src/components/shared/ModalOnboarding/ModalOnboarding.tsx @@ -10,8 +10,10 @@ import ProgressStepperSlim from 'components/ui/ProgressStepperSlim'; import Text from 'components/ui/Text'; import useModalStepperNavigation from 'hooks/helpers/useModalStepperNavigation'; import useOnboardingSteps from 'hooks/helpers/useOnboardingSteps'; +import useAntisybilStatusScore from 'hooks/queries/useAntisybilStatusScore'; import useIsContract from 'hooks/queries/useIsContract'; import useUserTOS from 'hooks/queries/useUserTOS'; +import useDelegationStore from 'store/delegation/store'; import useOnboardingStore from 'store/onboarding/store'; import useSettingsStore from 'store/settings/store'; @@ -28,6 +30,15 @@ const ModalOnboarding = (): ReactElement => { const { isConnected } = useAccount(); const { data: isUserTOSAccepted, isFetching: isFetchingUserTOS } = useUserTOS(); const { address } = useAccount(); + + const [isUserTOSAcceptedInitial, setIsUserTOSAcceptedInitial] = useState(isUserTOSAccepted); + + const { setIsTimeoutListPresenceModalOpen, isTimeoutListPresenceModalOpen } = useDelegationStore( + state => ({ + isTimeoutListPresenceModalOpen: state.data.isTimeoutListPresenceModalOpen, + setIsTimeoutListPresenceModalOpen: state.setIsTimeoutListPresenceModalOpen, + }), + ); const { setIsOnboardingDone, isOnboardingDone, @@ -52,7 +63,7 @@ const ModalOnboarding = (): ReactElement => { })); const { data: isContract } = useIsContract(); - const [isUserTOSAcceptedInitial, setIsUserTOSAcceptedInitial] = useState(isUserTOSAccepted); + const { data: antisybilStatusScore } = useAntisybilStatusScore(address); const stepsToUse = useOnboardingSteps(isUserTOSAcceptedInitial); @@ -87,6 +98,17 @@ const ModalOnboarding = (): ReactElement => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [setIsOnboardingDone, isUserTOSAccepted]); + useEffect(() => { + if ( + isOnboardingDone && + antisybilStatusScore?.isOnTimeOutList && + isTimeoutListPresenceModalOpen?.address !== address + ) { + setIsTimeoutListPresenceModalOpen({ address: address!, value: true }); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [address, isOnboardingDone, antisybilStatusScore?.isOnTimeOutList]); + useEffect(() => { if ( isConnected && diff --git a/client/src/components/shared/ModalTimeoutListPresence/ModalTimeoutListPresence.tsx b/client/src/components/shared/ModalTimeoutListPresence/ModalTimeoutListPresence.tsx index 1c4f6eac81..48d1a83acb 100644 --- a/client/src/components/shared/ModalTimeoutListPresence/ModalTimeoutListPresence.tsx +++ b/client/src/components/shared/ModalTimeoutListPresence/ModalTimeoutListPresence.tsx @@ -1,5 +1,6 @@ -import React, { FC, useState } from 'react'; +import React, { ReactElement, useState } from 'react'; import { Trans, useTranslation } from 'react-i18next'; +import { useAccount } from 'wagmi'; import BoxRounded from 'components/ui/BoxRounded'; import Button from 'components/ui/Button/Button'; @@ -7,27 +8,35 @@ import Img from 'components/ui/Img'; import InputCheckbox from 'components/ui/InputCheckbox'; import Modal from 'components/ui/Modal'; import { TIME_OUT_LIST_DISPUTE_FORM } from 'constants/urls'; +import useDelegationStore from 'store/delegation/store'; import styles from './ModalTimeoutListPresence.module.scss'; -import ModalTimeoutListPresenceProps from './types'; -const ModalTimeoutListPresence: FC = ({ modalProps }) => { +const ModalTimeoutListPresence = (): ReactElement => { const { t } = useTranslation('translation', { - keyPrefix: 'views.onboarding.suspectedSybilModal', + keyPrefix: 'views.onboarding.modalTimeoutListPresence', }); + const { address } = useAccount(); const [isChecked, setIsChecked] = useState(false); + const { setIsTimeoutListPresenceModalOpen, isTimeoutListPresenceModalOpen } = useDelegationStore( + state => ({ + isTimeoutListPresenceModalOpen: state.data.isTimeoutListPresenceModalOpen, + setIsTimeoutListPresenceModalOpen: state.setIsTimeoutListPresenceModalOpen, + }), + ); + return ( } + isOpen={!!isTimeoutListPresenceModalOpen && isTimeoutListPresenceModalOpen.value} isOverflowOnClickDisabled + onClosePanel={() => {}} showCloseButton={false} - {...modalProps} - isOpen >
= ({ modalProp
@@ -55,7 +64,7 @@ const ModalTimeoutListPresence: FC = ({ modalProp