diff --git a/scripts/govtool/.env.example b/.envrc.example similarity index 72% rename from scripts/govtool/.env.example rename to .envrc.example index c36704720..58f191c15 100644 --- a/scripts/govtool/.env.example +++ b/.envrc.example @@ -1,10 +1,6 @@ # vim: set ft=bash # This file is a template for the environment variables that need to be set and -# should be copied to `.env` or `.envrc` and filled in with the correct values. - -# Tell direnv to use Nix configuration. -# See https://direnv.net/docs/hook.html#nix. -use nix shell.nix +# should be copied to `.envrc` and filled in with the correct values. # target environment: dev, test, staging or beta export ENVIRONMENT= @@ -34,33 +30,25 @@ export GRAFANA_SLACK_RECIPIENT= export GRAFANA_SLACK_OAUTH_TOKEN= # the basic auth credentials for the Nginx proxy export NGINX_BASIC_AUTH= +# the IP addresses that bypass the basic auth +export IP_ADDRESS_BYPASSING_BASIC_AUTH1= +export IP_ADDRESS_BYPASSING_BASIC_AUTH2= - -# The following code is a nice way to display what environment we are in. -function warn() { - tput bold; tput setaf $2; echo "GovTool: $1"; tput sgr0 -} - -tput bold +# the domain name for the target environment case "$ENVIRONMENT" in "dev") export DOMAIN="dev-${CARDANO_NETWORK}.govtool.byron.network" - warn "This is a DEV environment, all good!" 2 ;; "test") export DOMAIN="test-${CARDANO_NETWORK}.govtool.byron.network" - warn "This is TEST environment, be careful…" 4 ;; "staging") export DOMAIN="staging.govtool.byron.network" - warn "This is STAGING environment, be careful…" 5 ;; "beta") export DOMAIN="sanchogov.tools" - warn "This is BETA environment, BE CAREFUL!" 1 ;; *) - warn "Unknown environment, exiting…" 1 exit 1 ;; esac diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 1c9509c9c..9e39de450 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -22,4 +22,4 @@ assignees: '' ## Expected behavior - \ No newline at end of file + diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 207145494..d8368d8e2 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -10,4 +10,4 @@ contact_links: - name: All issues url: https://github.com/IntersectMBO/govtool/issues - about: Check whether your issue is not already covered here. \ No newline at end of file + about: Check whether your issue is not already covered here. diff --git a/.github/ISSUE_TEMPLATE/feature_idea.yml b/.github/ISSUE_TEMPLATE/feature_idea.yml index 60b5902b0..67f24bc04 100644 --- a/.github/ISSUE_TEMPLATE/feature_idea.yml +++ b/.github/ISSUE_TEMPLATE/feature_idea.yml @@ -41,4 +41,4 @@ body: Which technical solutions, libraries or systems should be used, which components need to change, steps how to implement this, ... validations: - required: true \ No newline at end of file + required: true diff --git a/.github/dbsync-schema.sql b/.github/dbsync-schema.sql index 014727435..7f43b6589 100644 --- a/.github/dbsync-schema.sql +++ b/.github/dbsync-schema.sql @@ -987,4 +987,3 @@ FROM tx_out LEFT JOIN block ON tx.block_id = block.id WHERE tx_in.tx_in_id IS NULL AND block.epoch_no IS NOT NULL; - diff --git a/.github/workflows/build-and-deploy-beta.yml b/.github/workflows/build-and-deploy-beta.yml new file mode 100644 index 000000000..f4cba16ee --- /dev/null +++ b/.github/workflows/build-and-deploy-beta.yml @@ -0,0 +1,65 @@ +name: Build and deploy GovTool to BETA server +run-name: Deploy by @${{ github.actor }} + +# That should be executed on create: tag event +on: + workflow_dispatch: + +env: + ENVIRONMENT: "beta" + CARDANO_NETWORK: "sanchonet" + DOMAIN: "sanchogov.tools" + +jobs: + deploy: + name: Deploy app + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./scripts/govtool + env: + DBSYNC_POSTGRES_DB: "cexplorer" + DBSYNC_POSTGRES_USER: "postgres" + DBSYNC_POSTGRES_PASSWORD: "pSa8JCpQOACMUdGb" + GRAFANA_ADMIN_PASSWORD: ${{ secrets.GRAFANA_ADMIN_PASSWORD }} + GRAFANA_SLACK_RECIPIENT: ${{ secrets.GRAFANA_SLACK_RECIPIENT }} + GRAFANA_SLACK_OAUTH_TOKEN: ${{ secrets.GRAFANA_SLACK_OAUTH_TOKEN }} + NGINX_BASIC_AUTH: ${{ secrets.NGINX_BASIC_AUTH }} + SENTRY_DSN_BACKEND: ${{ secrets.SENTRY_DSN_BACKEND }} + TRAEFIK_LE_EMAIL: "admin+govtool@binarapps.com" + GTM_ID: ${{ secrets.GTM_ID }} + SENTRY_DSN: ${{ secrets.SENTRY_DSN_FRONTEND }} + PIPELINE_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} + IP_ADDRESS_BYPASSING_BASIC_AUTH1: ${{ secrets.IP_ADDRESS_BYPASSING_BASIC_AUTH1 }} + IP_ADDRESS_BYPASSING_BASIC_AUTH2: ${{ secrets.IP_ADDRESS_BYPASSING_BASIC_AUTH2 }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v3 + with: + aws-access-key-id: ${{ secrets.GHA_AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.GHA_AWS_SECRET_ACCESS_KEY }} + aws-region: eu-west-1 + + - name: Login to AWS ECR + uses: aws-actions/configure-aws-credentials@v2 + with: + aws-region: eu-west-1 + + - name: Setup SSH agent + uses: webfactory/ssh-agent@v0.8.0 + with: + ssh-private-key: ${{ secrets.GHA_SSH_PRIVATE_KEY }} + + - name: Deploy app + run: | + make --debug=b all + + - name: Reprovision Grafana + run: | + sleep 30 # give grafana time to start up + make --debug=b reload-grafana diff --git a/.github/workflows/build-and-deploy-dev.yml b/.github/workflows/build-and-deploy-dev.yml new file mode 100644 index 000000000..b620989ca --- /dev/null +++ b/.github/workflows/build-and-deploy-dev.yml @@ -0,0 +1,64 @@ +name: Build and deploy GovTool to DEV server +run-name: Deploy by @${{ github.actor }} + +on: + workflow_dispatch: + +env: + ENVIRONMENT: "dev" + CARDANO_NETWORK: "sanchonet" + DOMAIN: "dev-sanchonet.govtool.byron.network" + +jobs: + deploy: + name: Deploy app + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./scripts/govtool + env: + DBSYNC_POSTGRES_DB: "cexplorer" + DBSYNC_POSTGRES_USER: "postgres" + DBSYNC_POSTGRES_PASSWORD: "pSa8JCpQOACMUdGb" + GRAFANA_ADMIN_PASSWORD: ${{ secrets.GRAFANA_ADMIN_PASSWORD }} + GRAFANA_SLACK_RECIPIENT: ${{ secrets.GRAFANA_SLACK_RECIPIENT }} + GRAFANA_SLACK_OAUTH_TOKEN: ${{ secrets.GRAFANA_SLACK_OAUTH_TOKEN }} + NGINX_BASIC_AUTH: ${{ secrets.NGINX_BASIC_AUTH }} + SENTRY_DSN_BACKEND: ${{ secrets.SENTRY_DSN_BACKEND }} + TRAEFIK_LE_EMAIL: "admin+govtool@binarapps.com" + GTM_ID: ${{ secrets.GTM_ID }} + SENTRY_DSN: ${{ secrets.SENTRY_DSN_FRONTEND }} + PIPELINE_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} + IP_ADDRESS_BYPASSING_BASIC_AUTH1: ${{ secrets.IP_ADDRESS_BYPASSING_BASIC_AUTH1 }} + IP_ADDRESS_BYPASSING_BASIC_AUTH2: ${{ secrets.IP_ADDRESS_BYPASSING_BASIC_AUTH2 }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v3 + with: + aws-access-key-id: ${{ secrets.GHA_AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.GHA_AWS_SECRET_ACCESS_KEY }} + aws-region: eu-west-1 + + - name: Login to AWS ECR + uses: aws-actions/configure-aws-credentials@v2 + with: + aws-region: eu-west-1 + + - name: Setup SSH agent + uses: webfactory/ssh-agent@v0.8.0 + with: + ssh-private-key: ${{ secrets.GHA_SSH_PRIVATE_KEY }} + + - name: Deploy app + run: | + make --debug=b all + + - name: Reprovision Grafana + run: | + sleep 30 # give grafana time to start up + make --debug=b reload-grafana diff --git a/.github/workflows/build-and-deploy-staging.yml b/.github/workflows/build-and-deploy-staging.yml new file mode 100644 index 000000000..f53352b35 --- /dev/null +++ b/.github/workflows/build-and-deploy-staging.yml @@ -0,0 +1,66 @@ +name: Build and deploy GovTool to STAGING server +run-name: Deploy by @${{ github.actor }} + +on: + push: + branches: + - staging + +env: + ENVIRONMENT: "staging" + CARDANO_NETWORK: "sanchonet" + DOMAIN: "staging.govtool.byron.network" + +jobs: + deploy: + name: Deploy app + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./scripts/govtool + env: + DBSYNC_POSTGRES_DB: "cexplorer" + DBSYNC_POSTGRES_USER: "postgres" + DBSYNC_POSTGRES_PASSWORD: "pSa8JCpQOACMUdGb" + GRAFANA_ADMIN_PASSWORD: ${{ secrets.GRAFANA_ADMIN_PASSWORD }} + GRAFANA_SLACK_RECIPIENT: ${{ secrets.GRAFANA_SLACK_RECIPIENT }} + GRAFANA_SLACK_OAUTH_TOKEN: ${{ secrets.GRAFANA_SLACK_OAUTH_TOKEN }} + NGINX_BASIC_AUTH: ${{ secrets.NGINX_BASIC_AUTH }} + SENTRY_DSN_BACKEND: ${{ secrets.SENTRY_DSN_BACKEND }} + TRAEFIK_LE_EMAIL: "admin+govtool@binarapps.com" + GTM_ID: ${{ secrets.GTM_ID }} + SENTRY_DSN: ${{ secrets.SENTRY_DSN_FRONTEND }} + PIPELINE_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} + IP_ADDRESS_BYPASSING_BASIC_AUTH1: ${{ secrets.IP_ADDRESS_BYPASSING_BASIC_AUTH1 }} + IP_ADDRESS_BYPASSING_BASIC_AUTH2: ${{ secrets.IP_ADDRESS_BYPASSING_BASIC_AUTH2 }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v3 + with: + aws-access-key-id: ${{ secrets.GHA_AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.GHA_AWS_SECRET_ACCESS_KEY }} + aws-region: eu-west-1 + + - name: Login to AWS ECR + uses: aws-actions/configure-aws-credentials@v2 + with: + aws-region: eu-west-1 + + - name: Setup SSH agent + uses: webfactory/ssh-agent@v0.8.0 + with: + ssh-private-key: ${{ secrets.GHA_SSH_PRIVATE_KEY }} + + - name: Deploy app + run: | + make --debug=b all + + - name: Reprovision Grafana + run: | + sleep 30 # give grafana time to start up + make --debug=b reload-grafana diff --git a/.github/workflows/build-and-deploy-test.yml b/.github/workflows/build-and-deploy-test.yml new file mode 100644 index 000000000..8c4a3424c --- /dev/null +++ b/.github/workflows/build-and-deploy-test.yml @@ -0,0 +1,66 @@ +name: Build and deploy GovTool to TEST server +run-name: Deploy by @${{ github.actor }} + +on: + push: + branches: + - test + +env: + ENVIRONMENT: "test" + CARDANO_NETWORK: "sanchonet" + DOMAIN: "test-sanchonet.govtool.byron.network" + +jobs: + deploy: + name: Deploy app + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./scripts/govtool + env: + DBSYNC_POSTGRES_DB: "cexplorer" + DBSYNC_POSTGRES_USER: "postgres" + DBSYNC_POSTGRES_PASSWORD: "pSa8JCpQOACMUdGb" + GRAFANA_ADMIN_PASSWORD: ${{ secrets.GRAFANA_ADMIN_PASSWORD }} + GRAFANA_SLACK_RECIPIENT: ${{ secrets.GRAFANA_SLACK_RECIPIENT }} + GRAFANA_SLACK_OAUTH_TOKEN: ${{ secrets.GRAFANA_SLACK_OAUTH_TOKEN }} + NGINX_BASIC_AUTH: ${{ secrets.NGINX_BASIC_AUTH }} + SENTRY_DSN_BACKEND: ${{ secrets.SENTRY_DSN_BACKEND }} + TRAEFIK_LE_EMAIL: "admin+govtool@binarapps.com" + GTM_ID: ${{ secrets.GTM_ID }} + SENTRY_DSN: ${{ secrets.SENTRY_DSN_FRONTEND }} + PIPELINE_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} + IP_ADDRESS_BYPASSING_BASIC_AUTH1: ${{ secrets.IP_ADDRESS_BYPASSING_BASIC_AUTH1 }} + IP_ADDRESS_BYPASSING_BASIC_AUTH2: ${{ secrets.IP_ADDRESS_BYPASSING_BASIC_AUTH2 }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v3 + with: + aws-access-key-id: ${{ secrets.GHA_AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.GHA_AWS_SECRET_ACCESS_KEY }} + aws-region: eu-west-1 + + - name: Login to AWS ECR + uses: aws-actions/configure-aws-credentials@v2 + with: + aws-region: eu-west-1 + + - name: Setup SSH agent + uses: webfactory/ssh-agent@v0.8.0 + with: + ssh-private-key: ${{ secrets.GHA_SSH_PRIVATE_KEY }} + + - name: Deploy app + run: | + make --debug=b all + + - name: Reprovision Grafana + run: | + sleep 30 # give grafana time to start up + make --debug=b reload-grafana diff --git a/.github/workflows/code_check_backend.yml b/.github/workflows/code_check_backend.yml new file mode 100644 index 000000000..fca3fdf7c --- /dev/null +++ b/.github/workflows/code_check_backend.yml @@ -0,0 +1,64 @@ +name: Backend Lint & Format Check + +on: + workflow_dispatch: + push: + paths: + - govtool/backend/** + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - name: Use Haskell + uses: haskell-actions/setup@v2 + with: + ghc-version: '9.2.7' + cabal-version: '3.6.0.0' + + - name: Use Python + uses: actions/setup-python@v2 + with: + python-version: '3.8' + + - name: Install Pre-commit + run: pip install pre-commit + + - name: Install HLint + run: | + cabal update + cabal install hlint + + - name: Checkout code + uses: actions/checkout@v4 + + - name: Run hlint + run: pre-commit run --all-files hlint + + check-format: + runs-on: ubuntu-latest + steps: + - name: Use Haskell + uses: haskell-actions/setup@v2 + with: + ghc-version: '9.2.7' + cabal-version: '3.6.0.0' + + - name: Use Python + uses: actions/setup-python@v2 + with: + python-version: '3.8' + + - name: Install Pre-commit + run: pip install pre-commit + + - name: Install HLint + run: | + cabal update + cabal install stylish-haskell + + - name: Checkout code + uses: actions/checkout@v4 + + - name: Run hlint + run: pre-commit run --all-files stylish-haskell diff --git a/.github/workflows/code_check_frontend.yml b/.github/workflows/code_check_frontend.yml new file mode 100644 index 000000000..d9ac0bc1b --- /dev/null +++ b/.github/workflows/code_check_frontend.yml @@ -0,0 +1,80 @@ +name: Frontend Test, Lint & Type Check + +on: + push: + paths: + - govtool/frontend/** + - .github/workflows/test_frontend.yml + +defaults: + run: + working-directory: govtool/frontend + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Cache dependencies + uses: actions/cache@v2 + with: + path: govtool/frontend/node_modules + key: ${{ runner.os }}-node-${{ hashFiles('govtool/frontend/package-lock.json') }} + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version-file: "govtool/frontend/.nvmrc" + + - name: 🧪 Test + env: + NODE_OPTIONS: "--max_old_space_size=4096" + run: | + npm install + npm run test + + lint: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Cache dependencies + uses: actions/cache@v2 + with: + path: govtool/frontend/node_modules + key: ${{ runner.os }}-node-${{ hashFiles('govtool/frontend/package-lock.json') }} + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version-file: "govtool/frontend/.nvmrc" + + - name: 👕 Lint + run: | + npm install + npm run lint + + type_check: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Cache dependencies + uses: actions/cache@v2 + with: + path: govtool/frontend/node_modules + key: ${{ runner.os }}-node-${{ hashFiles('govtool/frontend/package-lock.json') }} + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version-file: "govtool/frontend/.nvmrc" + + - name: 🔍 Type Check + run: | + npm install + npm run tsc diff --git a/.github/workflows/deploy-dev-from-tag.yml b/.github/workflows/deploy-dev-from-tag.yml new file mode 100644 index 000000000..df9b4ba7f --- /dev/null +++ b/.github/workflows/deploy-dev-from-tag.yml @@ -0,0 +1,66 @@ +name: Build and deploy GovTool to DEV server +run-name: Deploy by @${{ github.actor }} + +on: + create: + +env: + ENVIRONMENT: "dev" + CARDANO_NETWORK: "sanchonet" + DOMAIN: "dev-sanchonet.govtool.byron.network" + +jobs: + deploy: + name: Deploy app + if: github.ref_type == 'tag' && startsWith(github.ref, 'refs/tags/dev-') + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./scripts/govtool + env: + DBSYNC_POSTGRES_DB: "cexplorer" + DBSYNC_POSTGRES_USER: "postgres" + DBSYNC_POSTGRES_PASSWORD: "pSa8JCpQOACMUdGb" + GRAFANA_ADMIN_PASSWORD: ${{ secrets.GRAFANA_ADMIN_PASSWORD }} + GRAFANA_SLACK_RECIPIENT: ${{ secrets.GRAFANA_SLACK_RECIPIENT }} + GRAFANA_SLACK_OAUTH_TOKEN: ${{ secrets.GRAFANA_SLACK_OAUTH_TOKEN }} + NGINX_BASIC_AUTH: ${{ secrets.NGINX_BASIC_AUTH }} + SENTRY_DSN_BACKEND: ${{ secrets.SENTRY_DSN_BACKEND }} + TRAEFIK_LE_EMAIL: "admin+govtool@binarapps.com" + GTM_ID: ${{ secrets.GTM_ID }} + SENTRY_DSN: ${{ secrets.SENTRY_DSN_FRONTEND }} + PIPELINE_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} + IP_ADDRESS_BYPASSING_BASIC_AUTH1: ${{ secrets.IP_ADDRESS_BYPASSING_BASIC_AUTH1 }} + IP_ADDRESS_BYPASSING_BASIC_AUTH2: ${{ secrets.IP_ADDRESS_BYPASSING_BASIC_AUTH2 }} + steps: + - name: Checkout code + uses: actions/checkout@v2 + with: + ref: ${{ github.ref }} + fetch-depth: 0 + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v3 + with: + aws-access-key-id: ${{ secrets.GHA_AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.GHA_AWS_SECRET_ACCESS_KEY }} + aws-region: eu-west-1 + + - name: Login to AWS ECR + uses: aws-actions/configure-aws-credentials@v2 + with: + aws-region: eu-west-1 + + - name: Setup SSH agent + uses: webfactory/ssh-agent@v0.8.0 + with: + ssh-private-key: ${{ secrets.GHA_SSH_PRIVATE_KEY }} + + - name: Deploy app + run: | + make --debug=b all + + - name: Reprovision Grafana + run: | + sleep 30 # give grafana time to start up + make --debug=b reload-grafana diff --git a/.github/workflows/lighthouse.yml b/.github/workflows/lighthouse.yml index 19c0047e4..bf1d30c45 100644 --- a/.github/workflows/lighthouse.yml +++ b/.github/workflows/lighthouse.yml @@ -9,7 +9,7 @@ on: jobs: lighthouse: runs-on: ubuntu-latest - env: + env: NODE_OPTIONS: --max_old_space_size=4096 steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/resync-cardano-node-and-db-sync.yml similarity index 76% rename from .github/workflows/build-and-deploy.yml rename to .github/workflows/resync-cardano-node-and-db-sync.yml index a7b892b9b..c193a07a9 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/resync-cardano-node-and-db-sync.yml @@ -1,15 +1,9 @@ -name: Build and deploy app -run-name: Deploy to ${{ inputs.environment }}/${{ inputs.cardano_network }} by @${{ github.actor }} +name: Re-sync Cardano Node and Cardano DB Sync +run-name: Cardano re-sync on ${{ inputs.environment }} by @${{ github.actor }} on: workflow_dispatch: inputs: - cardano_network: - required: true - type: choice - default: "sanchonet" - options: - - "sanchonet" environment: required: true type: choice @@ -19,18 +13,14 @@ on: - "test" - "staging" - "beta" - resync_cardano_node_and_db: - required: true - type: boolean - default: false env: ENVIRONMENT: ${{ inputs.environment || 'dev' }} - CARDANO_NETWORK: ${{ inputs.cardano_network || 'sanchonet' }} + CARDANO_NETWORK: "sanchonet" jobs: deploy: - name: Deploy app + name: Re-sync Cardano state runs-on: ubuntu-latest defaults: run: @@ -48,6 +38,8 @@ jobs: GTM_ID: ${{ secrets.GTM_ID }} SENTRY_DSN: ${{ secrets.SENTRY_DSN_FRONTEND }} PIPELINE_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} + IP_ADDRESS_BYPASSING_BASIC_AUTH1: ${{ secrets.IP_ADDRESS_BYPASSING_BASIC_AUTH1 }} + IP_ADDRESS_BYPASSING_BASIC_AUTH2: ${{ secrets.IP_ADDRESS_BYPASSING_BASIC_AUTH2 }} steps: - name: Checkout code uses: actions/checkout@v3 @@ -81,16 +73,10 @@ jobs: echo "DOMAIN=${DOMAIN:-$ENVIRONMENT-$CARDANO_NETWORK.govtool.byron.network}" >> $GITHUB_ENV fi - - name: Destroy Cardano Node, DB sync and Postgres if required - if: ${{ inputs.resync_cardano_node_and_db }} + - name: Destroy Cardano Node, DB sync and Postgres database run: | make --debug=b destroy-cardano-node-and-dbsync - name: Deploy app run: | make --debug=b all - - - name: Reprovision Grafana - run: | - sleep 30 # give grafana time to start up - make --debug=b reload-grafana diff --git a/.github/workflows/test_backend.yml b/.github/workflows/test_backend.yml index 125666f2a..eb17b9e46 100644 --- a/.github/workflows/test_backend.yml +++ b/.github/workflows/test_backend.yml @@ -38,7 +38,7 @@ jobs: run: | python -m pip install --upgrade pip pip install -r requirements.txt - pytest -v --github-report + pytest -v --github-report env: BASE_URL: https://${{inputs.deployment || 'staging.govtool.byron.network/api' }} METRICS_URL: https://metrics.cardanoapi.io diff --git a/.github/workflows/test_integration_cypress.yml b/.github/workflows/test_integration_cypress.yml index a0fbf9011..f6f5d554b 100644 --- a/.github/workflows/test_integration_cypress.yml +++ b/.github/workflows/test_integration_cypress.yml @@ -30,7 +30,7 @@ jobs: run: working-directory: ./tests/govtool-frontend runs-on: ubuntu-latest - env: + env: NODE_OPTIONS: --max_old_space_size=4096 steps: diff --git a/.github/workflows/toggle-maintenance.yml b/.github/workflows/toggle-maintenance.yml index 8c8949087..bdb5f5434 100644 --- a/.github/workflows/toggle-maintenance.yml +++ b/.github/workflows/toggle-maintenance.yml @@ -1,15 +1,9 @@ -name: Toggle (enable/disable) maintenance page -run-name: Maintenance mode set to ${{ inputs.maintenance }} on ${{ inputs.environment }}/${{ inputs.cardano_network }} by @${{ github.actor }} +name: Toggle maintenance page +run-name: Maintenance mode set to ${{ inputs.maintenance }} on ${{ inputs.environment }} by @${{ github.actor }} on: workflow_dispatch: inputs: - cardano_network: - required: true - type: choice - default: "sanchonet" - options: - - "sanchonet" environment: required: true type: choice @@ -22,33 +16,18 @@ on: maintenance: required: true type: choice - default: "enable" + default: "enabled" options: - - "enable" - - "disable" + - "enabled" + - "disabled" env: ENVIRONMENT: ${{ inputs.environment || 'dev' }} - CARDANO_NETWORK: ${{ inputs.cardano_network || 'sanchonet' }} + CARDANO_NETWORK: "sanchonet" jobs: - check_environment_exists: - name: Check if target environment exists before proceeding - runs-on: ubuntu-latest - defaults: - run: - working-directory: ./scripts/govtool - steps: - - name: Checkout code - uses: actions/checkout@v3 - - name: Check environment exists - run: | - make check-env-defined cardano_network=$CARDANO_NETWORK env=$ENVIRONMENT - toggle_maintenance: name: Toggl maintenance state - needs: - - check_environment_exists runs-on: ubuntu-latest defaults: run: @@ -56,23 +35,34 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v3 + - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v3 with: aws-access-key-id: ${{ secrets.GHA_AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.GHA_AWS_SECRET_ACCESS_KEY }} aws-region: eu-west-1 + - name: Login to AWS ECR uses: aws-actions/configure-aws-credentials@v2 with: aws-region: eu-west-1 + - name: Setup SSH agent uses: webfactory/ssh-agent@v0.8.0 with: ssh-private-key: ${{ secrets.GHA_SSH_PRIVATE_KEY }} + + - name: Set domain + run: | + if [[ "${{ inputs.environment }}" == "staging" ]]; then + echo "DOMAIN=staging.govtool.byron.network" >> $GITHUB_ENV + elif [[ "${{ inputs.environment }}" == "beta" ]]; then + echo "DOMAIN=sanchogov.tools" >> $GITHUB_ENV + else + echo "DOMAIN=${DOMAIN:-$ENVIRONMENT-$CARDANO_NETWORK.govtool.byron.network}" >> $GITHUB_ENV + fi + - name: Toggle maintenance run: | - make docker-login - if [[ "${{ inputs.environment }}" == "staging" ]]; then export DOMAIN=staging.govtool.byron.network; fi; - if [[ "${{ inputs.environment }}" == "beta" ]]; then export DOMAIN=sanchogov.tools; fi; - make toggle-maintenance cardano_network=$CARDANO_NETWORK env=$ENVIRONMENT maintenance=${{ inputs.maintenance || 'disable' }} + make toggle-maintenance maintenance=${{ inputs.maintenance || 'disabled' }} diff --git a/.gitignore b/.gitignore index 90e2f5b0f..2ce4f59f3 100644 --- a/.gitignore +++ b/.gitignore @@ -100,8 +100,7 @@ book/src/08_event-db/db-diagrams/*.dot # Used by nix result* -/.direnv/ -/.pre-commit-config.yaml +.direnv/ # Development Environments .vscode @@ -112,7 +111,6 @@ result* .std # nixago: ignore-linked-files -.prettierrc lefthook.yml treefmt.toml @@ -121,6 +119,7 @@ local/ # used by haskell govtool/backend/dist-newstyle/ +govtool/backend/.stack-work/ # target environment config dir scripts/govtool/config/target diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 000000000..e5d0bd8ac --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,24 @@ +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v3.2.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + +- repo: local + hooks: + - id: hlint + name: hlint + description: HLint gives suggestions on how to improve your source code. + entry: hlint + language: system + files: '\.l?hs$' + + - id: stylish-haskell + name: stylish-haskell + description: Haskell code format checker. + entry: stylish-haskell --config govtool/backend/.stylish-haskell.yaml + language: system + files: '\.l?hs$' diff --git a/CHANGELOG.md b/CHANGELOG.md index 0188ff91d..c555e2791 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,17 +10,9 @@ changes. ## [Unreleased] -- Add generate jsonld function [Issue 451](https://github.com/IntersectMBO/govtool/issues/451) -- Create GA review subbmision page [Issue 362](https://github.com/IntersectMBO/govtool/issues/362) -- Create GA creation form [Issue 360](https://github.com/IntersectMBO/govtool/issues/360) -- Create TextArea [Issue 110](https://github.com/IntersectMBO/govtool/issues/110) -- Choose GA type - GA Submiter [Issue 358](https://github.com/IntersectMBO/govtool/issues/358) -- Add on-chain inputs validation [Issue 377](https://github.com/IntersectMBO/govtool/issues/377) -- Add hash and validation of the metadata [Issue 378](https://github.com/IntersectMBO/govtool/issues/378) -- Add githubusercontent.com and ipfs.io to content security policy header [Issue 451](https://github.com/IntersectMBO/govtool/issues/451) - ### Added +- added `epochNo` and `date` to `drep/getVotes` and `proposal/get` - Added `isRegisteredAsSoleVoter` and `wasRegisteredAsSoleVoter` fields to the drep/info response [Issue 212](https://github.com/IntersectMBO/govtool/issues/212) - Abandoning registration as DRep [Issue 151](https://github.com/IntersectMBO/govtool/issues/151) - Abandoning GA creation [Issue 359](https://github.com/IntersectMBO/govtool/issues/359) @@ -29,9 +21,26 @@ changes. - Vitest unit tests added for utils functions [Issue 81](https://github.com/IntersectMBO/govtool/issues/81) - i18next library added to FE [Issue 80](https://github.com/IntersectMBO/govtool/issues/80) - Add possibility to vote on behalf of myself - Sole Voter [Issue 119](https://github.com/IntersectMBO/govtool/issues/119) +- Added Nix Flakes configurtion to handle unified developers setup [Issue 526](https://github.com/IntersectMBO/govtool/issues/526) +- Add missing test to utils [Issue 500](https://github.com/IntersectMBO/govtool/issues/500). +- DRep metadata builder [Issue 497](https://github.com/IntersectMBO/govtool/issues/497) +- Add generate jsonld function [Issue 451](https://github.com/IntersectMBO/govtool/issues/451) +- Create GA review subbmision page [Issue 362](https://github.com/IntersectMBO/govtool/issues/362) +- Create GA creation form [Issue 360](https://github.com/IntersectMBO/govtool/issues/360) +- Create TextArea [Issue 110](https://github.com/IntersectMBO/govtool/issues/110) +- Choose GA type - GA Submiter [Issue 358](https://github.com/IntersectMBO/govtool/issues/358) +- Add on-chain inputs validation [Issue 377](https://github.com/IntersectMBO/govtool/issues/377) +- Add hash and validation of the metadata [Issue 378](https://github.com/IntersectMBO/govtool/issues/378) +- Add githubusercontent.com and ipfs.io to content security policy header [Issue 451](https://github.com/IntersectMBO/govtool/issues/451) +- Add frontend test workflow on github actions [Issue 500](https://github.com/IntersectMBO/govtool/issues/500) +- Add type check & lint to github actions [Issue 512](https://github.com/IntersectMBO/govtool/issues/512) +- Add eslint & prettier to frontend package [Issue 166](https://github.com/IntersectMBO/govtool/issues/166) ### Fixed +- proposal/list search is case insensitive now [Issue 582](https://github.com/IntersectMBO/govtool/issues/582) +- proposal/list now takes optional `search` query param [Issue 566](https://github.com/IntersectMBO/govtool/issues/566) +- Fix possible sql error when there would be no predefined drep voting pwoer [Issue 501](https://github.com/IntersectMBO/govtool/issues/501) - Fix drep type detection when changing metadata [Issue 333](https://github.com/IntersectMBO/govtool/issues/333) - Fix make button disble when wallet tries connect [Issue 265](https://github.com/IntersectMBO/govtool/issues/265) - Fix drep voting power calculation [Issue 231](https://github.com/IntersectMBO/govtool/issues/231) @@ -44,9 +53,16 @@ changes. - Fixed deployment scripts to address [Issue 171](https://github.com/IntersectMBO/govtool/issues/171). - Fixed get drep voting power incorrectly executed endpoint [Issue 280](https://github.com/IntersectMBO/govtool/issues/280). - Fixed CSP settings to allow error reports with Sentry [Issue 291](https://github.com/IntersectMBO/govtool/issues/291). +- Fix frontend package tests [Issue 500](https://github.com/IntersectMBO/govtool/issues/500). +- Fix all the existing eslint errors [Issue 514](https://github.com/IntersectMBO/govtool/issues/514) +- Fix all the existing typescript errors [Issue 514](https://github.com/IntersectMBO/govtool/issues/514) +- Fix endless spinner on a dashboard [Issue 539](https://github.com/IntersectMBO/govtool/issues/539) ### Changed +- `proposal/list` allows user to search by tx hash [Issue 603](https://github.com/IntersectMBO/govtool/issues/603) +- `proposal/list` returns additional data such ass `expiryEpochNo`, `createdEpochNo`, `title`, `about`, `motivation`, + `rationale`. `TreasuryWithdrawals` GAs also got nicely formated details. [Issue 372](https://github.com/IntersectMBO/govtool/issues/372) - `drep/list` now return also `status` and `type` fields. Also it now returns the retired dreps, and you can search for given drep by name using optional query parameter. If the drep name is passed exactly, then you can even find a drep that's sole voter. [Issue 446](https://github.com/IntersectMBO/govtool/issues/446) - `drep/list` and `drep/info` endpoints now return additional data such as metadata url and hash, and voting power [Issue 223](https://github.com/IntersectMBO/govtool/issues/223) - `drep/info` now does not return sole voters (dreps without metadata) [Issue 317](https://github.com/IntersectMBO/govtool/issues/317) @@ -63,11 +79,18 @@ changes. - Applied unified policy on Docker images tagging [Issue 320](https://github.com/IntersectMBO/govtool/issues/320). - Reorganised deployment Makefiles in order to better document the process and easier management [Issue 385](https://github.com/IntersectMBO/govtool/issues/385). - Added a grafana panel to track all the deploys on the target machines [Issue 361](https://github.com/IntersectMBO/govtool/issues/361). +- Check a GA metadata is valid [Issue 535](https://github.com/IntersectMBO/govtool/issues/535) +- Update Cardano Serialization Lib to 12.0.0-alpha.19 [Issue 521](https://github.com/IntersectMBO/govtool/issues/521) +- Extend the eslint config to apply to the style guide of the project [Issue 514](https://github.com/IntersectMBO/govtool/issues/514) +- Update frontend package readme to reflect recent changes [Issue 543](https://github.com/IntersectMBO/govtool/issues/543) +- Change input selection strategy to 3 (random) [Issue 575](https://github.com/IntersectMBO/govtool/issues/575) ### Removed - +## [sancho-v1.0.2-alpha](https://github.com/IntersectMBO/govtool/releases/tag/sancho-v1.0.0-alpha) 2023-04-05 + ## [sancho-v1.0.0](https://github.com/IntersectMBO/govtool/releases/tag/sancho-v1.0.0) 2023-12-17 - Import code to new repository diff --git a/CODE-OF-CONDUCT.md b/CODE-OF-CONDUCT.md index 279206db0..8ceedd39a 100644 --- a/CODE-OF-CONDUCT.md +++ b/CODE-OF-CONDUCT.md @@ -129,4 +129,4 @@ at [https://www.contributor-covenant.org/translations][translations]. [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html [Mozilla CoC]: https://github.com/mozilla/diversity [FAQ]: https://www.contributor-covenant.org/faq -[translations]: https://www.contributor-covenant.org/translations \ No newline at end of file +[translations]: https://www.contributor-covenant.org/translations diff --git a/CODEOWNERS b/CODEOWNERS index a1a507b56..27885d4d5 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1,22 +1,22 @@ # GovTool Project Codeowners # These owners will be the default owners for everything in the repository. -* @kickloop @MSzalowski +* @Ryun1 @MSzalowski # Frontend assets templates -govtool/frontend/* @MSzalowski @Sworzen1 @JanJaroszczak @kickloop -*.tsx @MSzalowski @Sworzen1 @JanJaroszczak @kickloop -*.ts @MSzalowski @Sworzen1 @JanJaroszczak @kickloop -*.css @MSzalowski @Sworzen1 @JanJaroszczak @kickloop +govtool/frontend/* @MSzalowski @Sworzen1 @JanJaroszczak +*.tsx @MSzalowski @Sworzen1 @JanJaroszczak +*.ts @MSzalowski @Sworzen1 @JanJaroszczak +*.css @MSzalowski @Sworzen1 @JanJaroszczak # Backend -govtool/backend/* @MSzalowski @jankun4 @kickloop +govtool/backend/* @MSzalowski @jankun4 # DevOps -.github/workflows/* @placek @adgud @kickloop -config/govtool/* @placek @adgud @kickloop -infra/terraform/* @placek @adgud @kickloop +.github/workflows/* @placek @adgud +config/govtool/* @placek @adgud +infra/terraform/* @placek @adgud # Testing -gov-action-loader/* @IntersectMBO/govtool-test @kickloop -tests/* @IntersectMBO/govtool-test @kickloop \ No newline at end of file +tests/* @IntersectMBO/govtool-test +gov-action-loader/* @IntersectMBO/govtool-test diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e2ceb99de..a6e724ba7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -133,7 +133,7 @@ Examples: ### Commit Messages -Please make informative commit messages! +Please make informative commit messages! It makes it much easier to work out why things are the way they are when you’re debugging things later. A commit message is communication, so as usual, put yourself in the position of the reader: what does a reviewer, or someone reading the commit message later need to do their job? @@ -167,6 +167,7 @@ Try to keep branches up-to-date with main (not strict requirement though). Once merged to main, please delete the branch. **Tip:** Use Github's merge button in PRs to merge with commit. +This strategy helps us operate on the commits you've delivered: it's easier to [cherry-pick a merge commit](https://git-scm.com/docs/git-cherry-pick#Documentation/git-cherry-pick.txt--mltparent-numbergt) than a series of commits, and it's also easier to [revert changes using a merge commit](https://git-scm.com/docs/git-revert#Documentation/git-revert.txt--mparent-number) instead of a series of reverts. If a branch is outdated, use the rebase button in PRs to rebase feature branches (NOT update via merge). #### Rationale @@ -174,7 +175,7 @@ If a branch is outdated, use the rebase button in PRs to rebase feature branches Keeping branches ahead of main not only make the git history a lot nicer to process, it also makes conflict resolutions easier. Merging main into a branch repeatedly is a good recipe to introduce invalid conflict resolutions and loose track of the actual changes brought by a the branch. -### Versioning +### Versioning Not all releases are declared stable. Releases that aren't stable will be released as pre-releases and will append a -pre tag indicating it is not ready for running on production networks. @@ -201,7 +202,7 @@ Please see [CSS / SASS Style Guide](./docs/style-guides/css-sass/). #### Haskell -TODO +Please see [stylish-haskell configuration](./govtool/backend/.stylish-haskell.yaml). ## Bumping Node, DB-Sync, SanchoNet Versions @@ -229,21 +230,23 @@ TODO - Choose ticket/issue to work on from the project, move ticket from `todo` to `in progress`. - Create [well named](#branch-naming) branch from `develop` add changes, then make a pull request back to the `develop` branch. - If the changes are not ready for review then feel free to create a draft PR, and link this to the ticket/issue. +- When the PR is ready for review move the ticket from `in progress` to `in review`. Remember to change the state of the PR from draft to actual PR. - Developers should review each other's pull requests, and should be requested via [CODEOWNERS](./CODEOWNERS). - Unit tests are run on each pull request to `develop`. -- Once tests pass and peer review is done the branch can be merged into `develop` by author and then deployed to the dev environment (manually for now). -- The ticket status can then be moved ticket to `in QA` making sure that the PR/branch has been added to the ticket/issue as a comment. +- After a review remember to address all the requests of changes since they are blocking PR from being merged. +- Once tests pass and peer review is done the branch can be merged into `develop` by author and then deployed to the dev environment (manually). +- The ticket status can then be moved to `in QA` making sure that the PR/branch has been added to the ticket/issue as a comment. ### QA Workflow - Choose ticket from `in QA`. - Merge in the ticket's changes from `develop` branch into `test` branch. -- Deploy to test environment (manually for now). +- Deploy to test environment is performed automatically once the ticket is merged to the `test` branch. - The QA tests the deployed test environment against the ticket. - If QA agrees that the code is good, they can make a PR from `test` branch to `staging` branch where end-to-end and performance tests are run. -- If tests pass, then QA or tech lead can merge and deploy to staging environment (manually for now). +- If tests pass, then QA or tech lead can merge and deploy to staging environment (automatically). - Moving ticket to `staging` status this ready for PO check. - + ### PO Workflow - Choose ticket from `staging` status. diff --git a/LICENSE b/LICENSE index c38410929..b9e734694 100644 --- a/LICENSE +++ b/LICENSE @@ -198,4 +198,4 @@ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and - limitations under the License. \ No newline at end of file + limitations under the License. diff --git a/README.md b/README.md index 64806dc0f..5fe34a9ff 100644 --- a/README.md +++ b/README.md @@ -15,12 +15,14 @@
## 🌄 Purpose + The SanchoNet GovTool enables ada holders to experience some of the governance features described in [CIP-1694](https://github.com/cardano-foundation/CIPs/blob/master/CIP-1694/README.md) and to test governance features on [SanchoNet](https://sancho.network/) through a guided and straightforward experience. The SanchoNet GovTool is currently open for beta testing and can be accessed at [sanchogov.tools](https://sanchogov.tools/). Learn more; [docs.sanchogov.tools](https://docs.sanchogov.tools/). ## 📍 Navigation + - [GovTool Backend](./govtool/backend/README.md) - [GovTool Frontend](./govtool/frontend/README.md) - [GovTool deployment setup](./scripts/govtool/README.md) @@ -28,22 +30,28 @@ Learn more; [docs.sanchogov.tools](https://docs.sanchogov.tools/). - [Tests](./tests/) ### Utilities + - [Governance Action Loader](./src/governance-action-loader/) ## 🔩 Architecture + GovTool consists of a Haskell backend and a React Typescript frontend. ### Backend + GovTool backend implements an API wrapper around an instance of [DB-Sync](https://github.com/IntersectMBO/cardano-db-sync) which interfaces with a [Cardano Node](https://github.com/IntersectMBO/cardano-node). The API exposes endpoints making the querying of governance related data from DB-Sync straight forward. #### API Reference -[`Swagger documentation`]() + +[`Swagger documentation`](https://sanchogov.tools/api/swagger-ui/) ### Frontend + GovTool frontend web app communicates with the backend over a REST interface, reading and displaying on-chain governance data. Frontend is able to connect to Cardano wallets over the [CIP-30](https://github.com/cardano-foundation/CIPs/blob/master/CIP-0030/README.md) and [CIP-95](https://github.com/cardano-foundation/CIPs/blob/master/CIP-0095/README.md) standards. ## 🤝 Contributing + Thanks for considering contributing and helping us on creating GovTool! 😎 Please checkout our [Contributing Documentation](./CONTRIBUTING.md). diff --git a/SECURITY.md b/SECURITY.md index d7856a7fb..b7cf16805 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -12,4 +12,4 @@ Please provide a clear and concise description of the vulnerability, including: * steps that can be followed to exercise the vulnerability, * any workarounds or mitigations -If you have developed any code or utilities that can help demonstrate the suspected vulnerability, please mention them in your email but ***DO NOT*** attempt to include them as attachments as this may cause your Email to be blocked by spam filters. \ No newline at end of file +If you have developed any code or utilities that can help demonstrate the suspected vulnerability, please mention them in your email but ***DO NOT*** attempt to include them as attachments as this may cause your Email to be blocked by spam filters. diff --git a/SUPPORT.md b/SUPPORT.md index 29a3ade79..39f3878e2 100644 --- a/SUPPORT.md +++ b/SUPPORT.md @@ -10,4 +10,4 @@ See [`SECURITY.md`](SECURITY.md) on how to report a security vulnerability. # Contributions -See [`CONTRIBUTING.md`](CONTRIBUTING.md) on how to contribute. \ No newline at end of file +See [`CONTRIBUTING.md`](CONTRIBUTING.md) on how to contribute. diff --git a/docs/architecture/.gitignore b/docs/architecture/.gitignore index bce10f1e2..3434d617e 100644 --- a/docs/architecture/.gitignore +++ b/docs/architecture/.gitignore @@ -7,4 +7,4 @@ arch-structurizr/workspace.json #oura install oura/ -./oura/ \ No newline at end of file +./oura/ diff --git a/docs/architecture/arch-structurizr/README.md b/docs/architecture/arch-structurizr/README.md index 924bd9ce2..da08652d9 100644 --- a/docs/architecture/arch-structurizr/README.md +++ b/docs/architecture/arch-structurizr/README.md @@ -25,4 +25,4 @@ http://localhost:8080/workspace/documentation/* ## Decision Log ```bash http://localhost:8080/workspace/decisions/* -``` \ No newline at end of file +``` diff --git a/docs/architecture/arch-structurizr/dapp.dsl b/docs/architecture/arch-structurizr/dapp.dsl index dabfa3e1f..a1b1795bd 100644 --- a/docs/architecture/arch-structurizr/dapp.dsl +++ b/docs/architecture/arch-structurizr/dapp.dsl @@ -77,4 +77,3 @@ hwWallet -> cardanoWallet "integrates" // User's browser attaches to GVC FE browser -> dAppFrontEnd "connects" - diff --git a/docs/architecture/arch-structurizr/decisions/0001-decision b/docs/architecture/arch-structurizr/decisions/0001-decision index d01970187..5673ab07c 100644 --- a/docs/architecture/arch-structurizr/decisions/0001-decision +++ b/docs/architecture/arch-structurizr/decisions/0001-decision @@ -15,4 +15,3 @@ Accepted ## Consequences - diff --git a/docs/architecture/arch-structurizr/docker-compose.yml b/docs/architecture/arch-structurizr/docker-compose.yml index a55ac8367..ad387bf08 100644 --- a/docs/architecture/arch-structurizr/docker-compose.yml +++ b/docs/architecture/arch-structurizr/docker-compose.yml @@ -2,9 +2,9 @@ version: "3.6" services: voltaire-structurizr: - container_name: voltaire-dapp-architecture + container_name: voltaire-dapp-architecture image: structurizr/lite:latest ports: - '8080:8080' volumes: - - .:/usr/local/structurizr:rw \ No newline at end of file + - .:/usr/local/structurizr:rw diff --git a/docs/architecture/arch-structurizr/workspace.dsl b/docs/architecture/arch-structurizr/workspace.dsl index 096ccc31e..ee4cffc41 100644 --- a/docs/architecture/arch-structurizr/workspace.dsl +++ b/docs/architecture/arch-structurizr/workspace.dsl @@ -2,9 +2,9 @@ workspace "Voltaire Implementation Draft" { !docs ./documentation !adrs ./decisions !identifiers hierarchical - + model { - !include dapp.dsl + !include dapp.dsl } views { @@ -16,14 +16,14 @@ workspace "Voltaire Implementation Draft" { systemContext dAppFrontEnd { include * - autoLayout lr + autoLayout lr } systemContext dAppBackEnd { include * - autoLayout lr + autoLayout lr } - + container dAppBackEnd { include * autolayout lr @@ -73,12 +73,12 @@ workspace "Voltaire Implementation Draft" { element "Database" { shape Cylinder - } + } } branding { logo data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAfUAAABkCAMAAACo21lxAAAArlBMVEX///8AM60AMKwAKasAL6wAK6sALasAJqoZPK8AI6kAKKuruODc4PCls90AIakcS7dXc8Vmd8NGZcCkrtudptZogcoAHqj09vz5+/4AFacAGqg9Xb3Ez+wAOrHt8fq2weTY3vFzhcrP1+4ADabu8fnj6PV/kc9SbcIgQ7KNn9Y6VrjAyujM1O2+xuVScMQyULaUn9N3jM4AAKVue8M/Yb6Fl9ItTLUmRrNndsJjfsljQZZ0AAAWOElEQVR4nO1diXbiuLbFkgdswMy2MZMhmCkkhNchufz/jz1PR5ZsSYZUp26lrvfq7rUaT5L20dGZpDQaX8Ds9XwJv/JgjZ+LQMPI6P63W1Hjm9Dm/3w1FUXxn7nXwpn3jQ2q8e2YfSoDl3dha0SsO0vepbWuN2vd/4MRdjXkj3hX1v+nI2zw5rQ7wIrRq2f7z0UwRwr+h3upNZ3vAt6F8FNXcM36T8bKx3aHf8kNBcxeTcPnqv4aPwTutv84ga1t6xuaUqNGjRo1atSoUaNGjfsRHMYv1R74YnTbBt/fmBq/B625bdkrbmSWQvvsYLM5+y0tqvHtWNiqoigTbmSWwi4OzevKb2lSjW9Hx4noVLQB82NwuA2ZfNxij+LbJoIkXY0fhqFdZr1j2dhRTtQvi2bC+lPN+t+BhR3z6dAa/sWJf9LnNMWrWDjwx+9uXY1vwkg1LPOVtuZecTyvFftA/RYObNM8r39342p8F9bXC5t526sJ67hH/+i13lqL39quGr8V71rCunH9bzekxm9Ex49JR06tz/8OrF/uYdK7+Jqu8Yurire2Dvyiyhp/DK6Kjd7uuM8b7s67ewouNn3fNL+0EHgJvvJkDcB9Q/hiIkW1gn/xu61oLUDWgytBezm6Xsa9wfv77jJqrWvqH8fiZfi2XfV377v+avvWmcni6Ye4zN0e/otfX1niwnkuvGDURdgwMNZ0VdewgdX563NVEiDCet8EKBfJfUO1KcTn6+HEr+ruKLz79+/j0al9R+NibPNXfAaS++bpPfOjoL58hNIbVGFALGwNFM0wLKxF67CGLcPQ57sXYbl6Kw6+Oo9lUOQzsTWJ5jq+f64ve4qtJlG+HJEQvHcqJ/zIRADVkNw3dJAQqmb4+z6vPrRjcB/QsenMB9t7+ucqOnnMlllEWE1vMnr86yM7ve4IWD/1945WGEIFafbHKhC0bOfjyU0su6fr2cHHQ97JWV97evJvL8InvMuTPblbeQRTv9TeBLozr7AivCb1oExdDU3eB2gZ889l3dSxxA/o1uTYqpTKNLCdPSJLUmmkF3zZGGUdMLmsB7sJ5g6homJnzI+nuMObuP3uVTNUJZpJKJsN3lVPvoDxRSgp3mkYCDtY7M9c47Y3bfNYqklffOpmbScmoYr1+HG1NN4S1mPoxlQs+SkGOnW/L9GnZAyQeuJdl7HuDedY0kyj+3jJc9/JhAgZ6ahcDZAqZ/zw28q42HwhhSZPZZG/G9NbPRDeeAfrCnIOhacqWI8kxSw+wuKk0J3DK/GdueRr5w3nuoz1sXwII+m8w9sObsrnG3z5LVdRyI9Ng9Mkf53ZIY8Y2vtXojfeyqbbF62ZarQQ0j9hkX0Tf7bJ3GqJR/Ue1qP+FEankvVIIe94HAFWzAvQXNwVSt9ZN851MetenxlCJR5CnbWSkC8XzggvioVUZ5pqVrer5g8nsdgxNb30aapT13uMkKZXqTsOrtTbVEtTmtPp8bOJDOpnayB8GoYCtNFcqOIJ6wgXoFEdRBarWAjr9FMarbQjqRQ3r9GwEN0+meVBr3IOx7IUs76iBDpqp6ocp9PpWVENaqVHWgXtrpEMg71N/m9NzWxFPy4a4Z6WojSx7vUSknTlYR/7JX+9bncPy2wurDt9O+fdFsZ7suFUoU2WcP8NWFXovBqPx/G/gNezaZIuGaz3B6yjLnliddtNdd+gmLdvwm6npSkKakIfz8KRoFlH87LaFLJ+yGe6as9XrexRd90Zq0bO1ZPcj24Z2Uhu6I9lrQkbgUP9oNjJMIfZ6GuPTvbwg0w048i6aeGKNBlpAm9llg2qMsteowk3WMJcx0VTxPPc8NAEEtGe0cHAuka7U567WV/f1VxFCFdNr5dyaY1uGamq0J5jLFrto9QPEevrXGTxx4FdbcLtnrxW7Uo3mg+NbCgTZXcwaNZRxDptNWest7Pf8KNb3a5ETu1+yVYfEQ2FeQtdhEuqD3B/A6ayKeoazPUS6wnWH0A7uzufy3oCbzYn8wEpgq8GmWSgE/ga1lbQPpZ1xSjdJ2Dd2xGdaO/KzQg/SCtNWRCrsUzv09OFvUVPbfUcafg5reEniT5ZnJNBQziQvbiMNnkXV4nnq2qT+2JoSiRsI0M0WhmEcz17FQg37vNaUGY9wpUMjciKvKRv1Y+NMDOPkCay/Qreq1+cQALWW2TeYC6r3hhuQKrU2m4mDcgMj5A2QmPPw91R7UPNdIa+JaJs9mWv5WBExpq/NK5Ajg2uEsnmb7LumCCXglGVz/X8UypTDCZlvXElY8NfgiCEFIsivJ9nqCUosK7uA/a6gPUjKClDIHk5X2I9EyP4dLAxAcmhPeJJvCotac8NLMOVaZpPY5kPw4EH/gGa833ytp7pAsyVp6zH2q5B6nwULAhIVMz1xks2Nmz8TM56bj0b3HnWyp62g1xn6gOB5VGMVOF39jqf9TXoG30vsmhC4uGo0oCXN7pcgvyh3OBKxWlHRBwPCM/h4fDwVog1mAhFN5mgB1zwXLLnrDd2TPRbNiiJCHBQxfoaPH9EC2AF6wswRtUPzsoOtpweH/LjOSBV3MhbmXVwogB81g/AhcRrhqFRnAfMrk7qQyFtkqnhxaufTDLV5vX1AYClWDCcKSxBlCccMd1a1NNrCO34fLVRxXr4mT3PxPcqWM89HMzxi8BWMBKFCIpWZHkA6zpMMqQyRHJZd8GKhcAJt2vg14r6zkWw03zHV3p5IzpH23H886/Wz8FAiAOVxNzzy5bIJjPxUyvGI6PKb1UV620YGp2WwCrWG5A1sjgqfpux7iRvfAYbZsJ/E9A3eCdeJOOQDHmshyB10jz5FmyW411lre4mdL34MJrTcs0cSLMJl61wkdyxPm2+WP8QnjOxFh2CkxxylOYXOWfedDL7LLsE5prOj9tXsX7K5As9ouHzQCUn/LLJzA49tQ8Xx6y3Aj2bcY23M+Ia4x6l4aADNs060YWWTOu+wOrCCf6UENz2zsSZSnLc3vDs+L6/+1qJHMlMTIQiuBhkxQZ6aahgcqMsq+6Ch2Jw7YsqG74Dq80j1lw0oPBVv3wJs9MQpr4gkgSsrxq5B05ng7hz/ZK1T2gjpgA5squjaAclUaG68S4y/cKulbRVE3rJUhAR1MX3eIDSlVO2kBN/BKYd356rmOse2GU6YzpXsh7AwjAJipdAKj8zmtowL7XSrTGA9X+olBJCOU0j3lwfZMuaJV9sQakalTmYtwl82+AfNNnYnIlQ+l8pvwIi9OMXHiZTB4FNvCT2EE/bVbA+AkrYzGkl6wvwl0snbi4gXglOpwcLNt/yyC5q0e0HEv5RP8nQc1mHrxvy8QfvWyt2PhiOGKX/TEViHf5U7uW3II32R7zOeCXwT2hcoC2iIZUiG9Rct7lEonl+oFTDuyTeghDjGFWyTixSszjuh4ylvHqQLCJN3izSctYbPTKd8vM6eTb8AhwPUZQC2mLAYEXPLPKvDxXbRrSYv1LBGfTJmz0BHZqlwyhe38AWJdCnA/9gYZBAfiixAhCJpOqNrtC3KWdUyVy/eS6LTTg65kE2dtGtZr2fdaKoPDfZzEZN8sIQNLfG44hmPWySGAmG/vFYJz6ZLresOiThGIneUzfIfl7HXiLS8mc9nY62c5vZYvIy83ykE0LyOqDWxJ5wef0V1iEColAJ8Ta4MT5H0QDraP8+ZdG17Dxt+sQ+Ws06RFqL4c5T9j1anb+Cp8qLNNKsN1p5Zgd2GvFYz91NeYQMbE702eiqCqnHTF9JxRVDJlRk85wNug6QSTul+ol4kJEORBqvLb1Kd12MIKODVthEEngpuryqQtXVpNgE/sOUOBX0fzXrsEwVHfZMV6I5JUYnGDGHozsZ1qmlXT+nN/8K62S5njcUandq+koqdfQrrK9Y1gdYUZu8tvwK62DLMQsaWTg5K9KQrTLiA58LPuT9rBc6scg8DH1K/wjGF6/YjmXdOxMCjFQSf4l1soI1Lr5tw5g9xxUMyMyH0GPqrrjVEoyGV/f5hWX8Ml0Jsv+dzZ8sbhiGaPjHay7drLuIyTm4YIuUTKu76uaQWarQ+/JcH2UPsik2YlUdy3kqlvVGO1/aUwNdvq7fOdej2fd8yLXP1rSwsaKGcEDnU7lx8jZtzTFJp6tu2/O8u5sFPxcHS6KoZkIC8NIKua6+OFJWzbqGykXY1axDlAAz67oLthxbHtmGdCUuT6MC69HcIcOfpAa4rIMNr8mtOWpdZ/G8GjMl8S9UNYXNN7f6+UCieUBfOY3uqYYHd0mUJpMA6C1spXuGFpVjJpWs4yPHBqxm/T/c8D8kQIuLVxdiMa+lFxVZb4wJ7VocJ+Ox7n7c57m1chu+AqM8HMzzhCJspsS7mwgj6RKAPyEpIRQgzNqmvbIuIUwxjqlA1nWks4CR5WbwK1knSS92UYHFyyjMQbIqPpWC0CXWvSNRt+ZYkHO7M0pDfNrqyMjFSL6qGl1RmDzcJxFZhPGvRWTRo0/C+pjEY7z4Hy/deQfqo5zvzj23wTuFwTQbNxI4ZVDJegizjYnNgeZFc7p5Xlywkt1uljaOU7G5DNQOCmfGz76QRKN8Hzqxm+/wkZcD33f85lCWfZk6/kTpf+1oyRPYK0/iLwTLDDSLkM1S1PcBiym8suR1kChNUQtAEQ63HKaS9RPMxwmToIUpvS+0b0BKpN+Ltk5prkevIVVL+ruXFbEWsi8kvCnNvoD4cH2xIrzN6TmJ3y2CViukm7mJfpiFyS3rjfvFTGsbBN8R2yJbR0vg00vIC3FE1IK6JnZvadUQxuGJ1cRzoitZh7grG5E45u3grycReUUrojzXc/Ml1g2wODBznSzY3CAvYAHSo90RJyfYdiMnz5nmgrKcOrbt73+xqoKsiZJUECkypBUoHS0WoXgIg5B1F3Q8rzCiknWo/GMs0mBSbk8RJceFx/riM6/TH4MBQbNO8ni8cGTeC5CN/f2HQZ72ZtI1fdJLp7vXm6SF0Hbz184lAv1UVncAUudO19IsnqoHtRQhFefclplpiPRyb6pYn5FUHa2LLndIZamGnqPhI1/AyneCwJdo5shaJ8tgkSrZijTX7HAgbaLyqUYqnxeiYPGRKJb15VJ9xEABeSWISFIDWJF8SoO9VQdc4q0erCSJWfdgBwQnPF7Butcn1ZyUxJAaISmsgtnNZb3xZhSfY6sloThKsGMgwQzeId1/u+g9mflZQlvqu5M4uDCjphopp+hNDOxPHzxN1gVJFnrssGyq1A6gzXu+OkpQqLmS5NehgLno/TcqWV+CO8gct9u6J/hbqm/ksx7Hs1mwrJNaHn7teAJSMu/LyqzeY56Rk8rigt6AnZiK/9A5WCP16tJkwcPHyZJdDoKtUiEUvtL6+pRv5uABmls47VrCuncUTnY56ydS9MKYHWSUpe0rFoAKWA8/C4qjsAuCBEiFZc9k1zCWKfh1tuMp1d4neltbHJpl97Sm3YW6cO1BF24JDVKbPDVBkmhMqvdGlFqXB1LXzpYGymppOrDS2MXJLmV9vQd69TM1bwPSKW77ujB0BR9SwHrjuWDFFFgnO1pRk79M5klbSxbAA2fTSPQBs6dVscNGmzl9IY02wPYKfpZFAhLt1zlt9nrQI3pUYXObaL/MguySYlYxGevEjNeKW9ElrC9G+T52gx5PiBSJignheiEzKGKdWWLLrG9yzWLxHOA3LFB+BWQObDaqnWI+tcB6UuqR/W2AqnBwGcTQUPR5q+ByBlPuAT3Dquj9gFhY9MIprZvrgGirhayIiHUvPJzzveHMuQUbcObMgN8+oj3ZzXtC1qldqxzWqe2nSL0UJ0K7n28H16XO+ia90Uh3ujCnFsR7WkmSOEG6pzU9Jf4rpxZcqKKRc4cS/tNKI6Tr51wgPJiX/D8I3qBMLMaek1dLgiRrhcJNYF0dnGaAl85oNbDMfLnVGTcYHEFNUGOaL8VsJEnIeqPNnNhSOrUgL19Exny7zL/abt1wLjClM3cKWGINISuLS7tniuRkKbpSGkftpjy7R1uNvvl4Xbz3mWd0dUM5j6/D4ei6ms71vL3MIZWwN04tFkAQkO0GzADKWSch1ILln++lzsNrmmFgWvBVzEwiUDXihMiImxkUs95Y0vuKS6wv8ixNnBJRPvqj4XB42O2RRSkJ6TkqCU5TTb/By0f0aUTx8Ad+LnpE9YaHffP2oOOWfmtOjSBSsRE5jdGoMtJNj9+Km89mAPEERJuIctaJAivM0DtOI9IQo6hJMbsh3NqR30IHNyWsN27URCufS7NWaCGMxtCMx1BjNIQlPcmLA3LCEZw8tiThZPvRDesczBTxaXPJV5lae1JHMBGHBUlA1ORsHREU7gyJGc9wWMk6Mj7YhkCEQVY0ALEd5tAQGetubuLwTh4L9hXNROaAR/p6KF7q3VWyOiBMKqc7n1ZcUoutrTDm77bGwzsPWl0fZW3W5oxfkNeUS94IixJ9U9XeF5h/apf5XMVwasqqEEyG95iSzUUkJmlT64mM9UY7P3yGd95cu2ciRQxVX/EMrjffftqKLbGXnv/0hG65UC+2Xf/JWomXcbc3wZPenbQvxk+C0zAV3SlUuED0VHokK6mboYoaqnY3kmMzGMtaynp8CGlR4zwTs0MSB/PAeKRrKaWsU1Fo/omib4Yh4l13+PbWKZYU+QnPDx7Znhy+4999ItV6hTiNRpozLZhE4FHIws78DdtVuxs3IE/6BzV7O6UwePZ9pFv2J2eDDyntktaZgEFMHxWT7YkWRVbJsRg233pabPc25zBehM2joEAiiU19bYeiAIkJaN/zhwYyBJemHhlxKpggCBvafPdSzMVt/fSgP1teatdz0tsMTGbccJL+5IhKM9+yV+MnSo10fKt4KGF6LGFzeuEdLh4oRnKL9STNRs6s7DY/t+fs9CfnP/xHFp/ZI8I/mBgu3xXL0LKTORFSI9NYU24zkRl3ik+KvX9m3oFYe6BS4YAcs852cN4rlj+Z+Npnf/hSXiC8ZStFJ5C+KujAfWHpJ1HQ2GsBqMW23eLgZRaI1HcIX6koWlmSb5F5CL+IVC5piuTwn7B17R27Td2emKjZnY5HnDHMcTFMX342cwr3MD2O7+LyoNpK5bbZEjzX3aT4an3O/zzIELpu1Rh6L9fqk87j4hdf1y18V4rldNffD6rx5+OWxl2bv3YGUY2fhcw0FkbAa/wFCFtvjN6HHGfhdLjh5Rr81nbV+EbM9qZp72i7PysdZ/6OxWJnW6byla0vNf5EJIdMO3SC4C2JkWBmQ2ZSO6Xad4bhavzhSMNg6gfFp3eYGNhhY41pzLte6v8SBBnrTGhnfb0V/uz2sWb9r0KST7Wr9pYnWr/W8H8Nnucm9oW7UgDuysH2/NG/AVHjj8X60u9UV2B4y/Fb8P2NqVGjRo0aNWrUqFGjhgSt7eNRl2XvWldC/GR0HFO0wX0TCqh9s7H/b1be1fjNcAeaovP/qufoOO9xL3jRMyr3QPEaPwPuACsW9yir5ZOOrDn3oauD8H1/QqjGn4l2U59z62+TTWacP7LViPdRK+S0+Ro/Eu6Mr6tX8VYQ0Q6zr+xtrPEDcHI0ZL5X31fjr8Jy9yHe0Vjjz8f/A58flJOaxNzMAAAAAElFTkSuQmCC - # font - } + # font + } } -} \ No newline at end of file +} diff --git a/docs/architecture/sequence-diagrams/raw/delegation.txt b/docs/architecture/sequence-diagrams/raw/delegation.txt index 30501025e..689907360 100644 --- a/docs/architecture/sequence-diagrams/raw/delegation.txt +++ b/docs/architecture/sequence-diagrams/raw/delegation.txt @@ -10,4 +10,4 @@ dApp Frontend->Wallet:""API.submitDelegation(dRepID, PubStakeKey)"" Wallet->User: Ask permission popup (Wallet UI) User->Wallet: Access granted (Wallet UI) Wallet->Cardano Node: Submit transaction: \n""POST /delegation/{delegation-cert} -Wallet->dApp Frontend: ""SignedDelegationCertificate"" \ No newline at end of file +Wallet->dApp Frontend: ""SignedDelegationCertificate"" diff --git a/docs/architecture/sequence-diagrams/raw/drep-registration.txt b/docs/architecture/sequence-diagrams/raw/drep-registration.txt index eac9db47a..5e701117f 100644 --- a/docs/architecture/sequence-diagrams/raw/drep-registration.txt +++ b/docs/architecture/sequence-diagrams/raw/drep-registration.txt @@ -12,4 +12,4 @@ dApp Frontend->Wallet: Pass certificate to wallet:\n""API.submitDRepRegistration Wallet->User: Ask permission popup (Wallet UI) User->Wallet: Access granted (Wallet UI) Wallet->Cardano Node: Submit transaction: \n""POST /registration/{registration-cert} -Wallet->dApp Frontend: ""SignedDRepRegistrationCertificate"" \ No newline at end of file +Wallet->dApp Frontend: ""SignedDRepRegistrationCertificate"" diff --git a/docs/architecture/sequence-diagrams/raw/drep-retirement.txt b/docs/architecture/sequence-diagrams/raw/drep-retirement.txt index cbd99a2a4..99a43a9ae 100644 --- a/docs/architecture/sequence-diagrams/raw/drep-retirement.txt +++ b/docs/architecture/sequence-diagrams/raw/drep-retirement.txt @@ -11,4 +11,4 @@ dApp Frontend->Wallet: Pass certificate to wallet:\n""API.submitDRepRetirementCe Wallet->User (dRep): Ask permission popup (Wallet UI) User (dRep)->Wallet: Access granted (Wallet UI) Wallet->Cardano Node: Submit transaction: \n""POST /retirement/{retirement-cert} -Wallet->dApp Frontend: ""SignedDRepRetirementCertificate"" \ No newline at end of file +Wallet->dApp Frontend: ""SignedDRepRetirementCertificate"" diff --git a/docs/architecture/sequence-diagrams/raw/drep-status.txt b/docs/architecture/sequence-diagrams/raw/drep-status.txt index 398a99bdb..e668837c8 100644 --- a/docs/architecture/sequence-diagrams/raw/drep-status.txt +++ b/docs/architecture/sequence-diagrams/raw/drep-status.txt @@ -8,4 +8,4 @@ dApp Frontend->Wallet: ""API.getDRepKey()"" Wallet->dApp Frontend: ""pubDRepKey"" dApp Frontend->dApp Backend: ""GET drep/{pubDRepKey} -dApp Backend->dApp Frontend: ""bool"" \ No newline at end of file +dApp Backend->dApp Frontend: ""bool"" diff --git a/docs/architecture/sequence-diagrams/raw/login.txt b/docs/architecture/sequence-diagrams/raw/login.txt index 8340cc37a..4a772d98a 100644 --- a/docs/architecture/sequence-diagrams/raw/login.txt +++ b/docs/architecture/sequence-diagrams/raw/login.txt @@ -33,4 +33,4 @@ Wallet->dApp Frontend: ""pubDRepKey"" dApp Frontend->dApp Backend: ""GET drep/{pubDRepKey} dApp Backend->dApp Frontend: ""DRepCert"" -dApp Frontend->User: Serve correct UI \ No newline at end of file +dApp Frontend->User: Serve correct UI diff --git a/docs/architecture/sequence-diagrams/raw/voting.txt b/docs/architecture/sequence-diagrams/raw/voting.txt index 2c311ed14..1767720c7 100644 --- a/docs/architecture/sequence-diagrams/raw/voting.txt +++ b/docs/architecture/sequence-diagrams/raw/voting.txt @@ -13,4 +13,4 @@ dApp Frontend->Wallet: Pass object to wallet:\n""API.submitVote(Vote)"" Wallet->User (dRep): Ask permission popup (Wallet UI) User (dRep)->Wallet: Access granted (Wallet UI) Wallet->Cardano Node: Submit transaction: \n""POST /vote/{vote} -Wallet->dApp Frontend: ""SignedVote"" \ No newline at end of file +Wallet->dApp Frontend: ""SignedVote"" diff --git a/docs/architecture/sequence-diagrams/raw/wallet-connect.txt b/docs/architecture/sequence-diagrams/raw/wallet-connect.txt index 3119dc48c..6348fbce9 100644 --- a/docs/architecture/sequence-diagrams/raw/wallet-connect.txt +++ b/docs/architecture/sequence-diagrams/raw/wallet-connect.txt @@ -10,4 +10,4 @@ User->dApp Frontend: Wallet Selected ""walletName dApp Frontend->Wallet: ""cardano.{walletName}.enable({"cip": ?}) Wallet->User: Ask permission popup (Wallet UI) User->Wallet: Access granted (Wallet UI) -Wallet->dApp Frontend: ""API"" object \ No newline at end of file +Wallet->dApp Frontend: ""API"" object diff --git a/docs/operations/HANDLE_NEW_GOVERNANCE_ACTION_TYPE.md b/docs/operations/HANDLE_NEW_GOVERNANCE_ACTION_TYPE.md new file mode 100644 index 000000000..a0c2ce2b7 --- /dev/null +++ b/docs/operations/HANDLE_NEW_GOVERNANCE_ACTION_TYPE.md @@ -0,0 +1,62 @@ +# Overview + +This document describes the process of adding a new governance action type to the frontend application. + +## Prerequisites + +Every governance action should follow the [CIP-100](https://github.com/cardano-foundation/CIPs/tree/master/CIP-0100) and [CIP-108](https://github.com/cardano-foundation/CIPs/pull/632) (currently on a PR stage) standards. + +Person to contact: @Ryun1 + +## Package + +All the related changes are to be made under the `govtool/frontend` directory. + +## Steps + +### Type declarations + +1. Add new `GovernanceActionType` enum property to the `govtool/frontend/src/types/governanceAction.ts:4`. +2. If the governance action requires a new field - add it to the `govtool/frontend/src/types/governanceAction.ts:8`. +3. Create a new governance action field schema. Every governance action schema should extend from the [SharedGovernanceActionFieldSchema](govtool/frontend/src/types/governanceAction.ts:25). +4. Add a new governance action schema to the below the [SharedGovernanceActionFieldSchema](govtool/frontend/src/types/governanceAction.ts:25). +5. Add a new governance action schema to the union type of [GovernanceActionFieldSchemas](govtool/frontend/src/types/governanceAction.ts:40). +6. Update the `docs/oprations/HANDLE_NEW_GOVERNANCE_ACTION_TYPE.md` with all the new declarations provided (eg.: line numbers, new types configurations). + +### Fields declaration + +1. Add new governance action field declaration to the [GOVERNANCE_ACTION_FIELDS](govtool/frontend/src/constants/governanceActionFields.ts:88) object. + +### Custom validations + +If the field require some custom validation, add a new validation function to the [Validations](govtool/frontend/src/utils/govActionValidations/index.ts:4) object. + +### Constants & Fields definitions + +[GovernanceActionFieldSchemas](govtool/frontend/src/types/governanceAction.ts:40) - includes all the governance action field schemas which are: + +- component - the component which should be used to render the field (currently supporting are: 'Input', 'TextArea' and array of both of them). +- labelI18nKey - the i18n key for the field label. +- placeholderI18nKey - the i18n key for the field placeholder. +- tipI18nKey - the i18n key for the field tip. +- rules - the array of validation rules for the field [check rules property in react-hook-form](https://www.react-hook-form.com/api/usecontroller/controller/#:~:text=cleared%20value%20instead.-,rules,-Object). + +[SharedGovernanceActionFieldSchema](govtool/frontend/src/types/governanceAction.ts:25) - includes all the shared fields for the governance action - each field is of type [FieldSchema](govtool/frontend/src/types/governanceAction.ts:14) which corresponds to [CIP-108](https://github.com/cardano-foundation/CIPs/pull/632). + +### How this works + +Every governance action field is a part of the governance action schema. +The schema is used to render appropriate fields in a [form](govtool/frontend/src/components/organisms/CreateGovernanceActionSteps/CreateGovernanceActionForm.tsx). +Logic behind validation and hashing is also based on the schema and is handled in the [useCreateGovernanceActionForm](govtool/frontend/src/hooks/forms/useCreateGovernanceActionForm.ts). + +Defining the new Governance action type in [Type declarations](#type-declarations) will allow the application to handle the new governance action type. + +Defining the new Governance action field in [Fields declaration](#fields-declaration) will allow the application to render the new governance action field. + +They both are used in the [CreateGovernanceActionForm](govtool/frontend/src/components/organisms/CreateGovernanceActionSteps/CreateGovernanceActionForm.tsx) component. + +### Testing + +After defining a new governance action type and field, it is important to test the new governance action type and field in the [CreateGovernanceActionForm](govtool/frontend/src/components/organisms/CreateGovernanceActionSteps/CreateGovernanceActionForm.tsx) component. + +On the application it is approachable under `/create_governance_action` route. diff --git a/docs/operations/README.md b/docs/operations/README.md index bfe06e2dc..4b6ab139f 100644 --- a/docs/operations/README.md +++ b/docs/operations/README.md @@ -64,4 +64,3 @@ Deploying new versions of the application is done using Github actions 4. From the droping options - "Run workflow", select the branch, Cardano network and type of environment for your deployment 5. Press "Run workflow" 6. Wait for the final effect. It's done. - diff --git a/flake.lock b/flake.lock new file mode 100644 index 000000000..fb4213fd1 --- /dev/null +++ b/flake.lock @@ -0,0 +1,78 @@ +{ + "nodes": { + "default_nixpkgs": { + "locked": { + "lastModified": 1679410443, + "narHash": "sha256-xDHO/jixWD+y5pmW5+2q4Z4O/I/nA4MAa30svnZKK+M=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "c9ece0059f42e0ab53ac870104ca4049df41b133", + "type": "github" + }, + "original": { + "owner": "nixos", + "repo": "nixpkgs", + "rev": "c9ece0059f42e0ab53ac870104ca4049df41b133", + "type": "github" + } + }, + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1710146030, + "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "node_nixpkgs": { + "locked": { + "lastModified": 1696748673, + "narHash": "sha256-UbPHrH4dKN/66EpfFpoG4St4XZYDX9YcMVRQGWzAUNA=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "9957cd48326fe8dbd52fdc50dd2502307f188b0d", + "type": "github" + }, + "original": { + "owner": "nixos", + "repo": "nixpkgs", + "rev": "9957cd48326fe8dbd52fdc50dd2502307f188b0d", + "type": "github" + } + }, + "root": { + "inputs": { + "default_nixpkgs": "default_nixpkgs", + "flake-utils": "flake-utils", + "node_nixpkgs": "node_nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 000000000..e732205ec --- /dev/null +++ b/flake.nix @@ -0,0 +1,22 @@ +{ + description = "GovTool and utilities monorepo."; + + inputs.default_nixpkgs.url = "github:nixos/nixpkgs/c9ece0059f42e0ab53ac870104ca4049df41b133"; + inputs.node_nixpkgs.url = "github:nixos/nixpkgs/9957cd48326fe8dbd52fdc50dd2502307f188b0d"; + inputs.flake-utils.url = "github:numtide/flake-utils"; + + outputs = { self, default_nixpkgs, node_nixpkgs, flake-utils, ... }: + flake-utils.lib.eachDefaultSystem (system: + let + defaultPkgs = import default_nixpkgs { inherit system; config.allowBroken = true; }; + nodePkgs = import node_nixpkgs { inherit system; }; + in + { + packages.scripts = defaultPkgs.callPackage ./scripts/govtool { pkgs = defaultPkgs; }; + packages.infra = defaultPkgs.callPackage ./infra/terraform { pkgs = defaultPkgs; }; + packages.backend = defaultPkgs.callPackage ./govtool/backend { pkgs = defaultPkgs; }; + packages.frontend = nodePkgs.callPackage ./govtool/frontend { pkgs = nodePkgs; }; + + devShell = defaultPkgs.mkShell { buildInputs = [ defaultPkgs.pre-commit ]; }; + }); +} diff --git a/gov-action-loader/backend/.env.example b/gov-action-loader/backend/.env.example index a9b58410d..654ec757e 100644 --- a/gov-action-loader/backend/.env.example +++ b/gov-action-loader/backend/.env.example @@ -4,4 +4,3 @@ KUBER_API_KEY=xxxxxxxxxxxxx ## Not required anymore BLOCKFROST_API_URL= BLOCKFROST_PROJECT_ID= - diff --git a/gov-action-loader/backend/.gitignore b/gov-action-loader/backend/.gitignore index 69188450f..4c79d15bd 100644 --- a/gov-action-loader/backend/.gitignore +++ b/gov-action-loader/backend/.gitignore @@ -2,4 +2,4 @@ .venv venv .vscode -__pycache__ \ No newline at end of file +__pycache__ diff --git a/gov-action-loader/backend/Dockerfile b/gov-action-loader/backend/Dockerfile index 3115980da..d0ced0e73 100644 --- a/gov-action-loader/backend/Dockerfile +++ b/gov-action-loader/backend/Dockerfile @@ -13,4 +13,4 @@ COPY ./requirements.txt ./requirements.txt RUN pip install --no-cache-dir --upgrade -r ./requirements.txt COPY . /app EXPOSE 8000 -CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] \ No newline at end of file +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/gov-action-loader/backend/README.md b/gov-action-loader/backend/README.md index ef636cd86..52cf07d6a 100644 --- a/gov-action-loader/backend/README.md +++ b/gov-action-loader/backend/README.md @@ -9,10 +9,10 @@ This repository helps in creation of Conway era transactions containing proposa - Sanchonet Faucet API ### Limitations -Gov action loader backend instance uses fixed set of wallet to perform transactions. This means that gov action loader can be used by only 1 user at a time. +Gov action loader backend instance uses fixed set of wallet to perform transactions. This means that gov action loader can be used by only 1 user at a time. -## 1. Setup +## 1. Setup ``` python3 -m venv ./venv @@ -22,7 +22,7 @@ pip install -r requirements.txt ## 1. Initialize Wallets -### Prerequisite +### Prerequisite - You should have sancho-node, kuber server runnning and cardano-cli available. For bulk proposal creation , multiple wallets must be generated. Generate them with following command. @@ -34,4 +34,4 @@ python3 wallets.py ## 2. Start the server - Add .env file with all the keys from .env.example -- Run `uvicorn app.main:app --reload --env-file .env` in the repository to run the program \ No newline at end of file +- Run `uvicorn app.main:app --reload --env-file .env` in the repository to run the program diff --git a/gov-action-loader/backend/requirements.txt b/gov-action-loader/backend/requirements.txt index 7f65dd75c..65e8a2cea 100644 --- a/gov-action-loader/backend/requirements.txt +++ b/gov-action-loader/backend/requirements.txt @@ -4,4 +4,4 @@ uvicorn[standard] pydantic-settings httpx python-dotenv -pycardano \ No newline at end of file +pycardano diff --git a/gov-action-loader/backend/wallets.json b/gov-action-loader/backend/wallets.json index 5ec44eff8..22c4a3f9b 100644 --- a/gov-action-loader/backend/wallets.json +++ b/gov-action-loader/backend/wallets.json @@ -1 +1 @@ -[{"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58200a2e6f6f040636a56b61276487459b9e9f309d09d1d57319b41ed32f3053af8d"}, "address": "addr_test1qzc97zml9xzhm7xcsmqretkk7ztzyehj3dpd7ph7h0s40wp0ea9f3e353pmmr7yxv7m2dj09rn44m7pvd0m4cylusn8szlm75t"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58205a06b43ec9c2b0569e7ab12d34d54238b08dad06c46b37acc4cb560a41dd1b0a"}, "address": "addr_test1qpar26cedvu2adsseslkvedd8p0spdyah9xugr956mrczj2lhcns9veuwczx5zymn3mwfzz43zetmx6rxc9lyxuquvpq5c283g"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58204e0d6d81b89cca3fc8d7a34c0e6e034154b7bfeaa63c20d8fb87a104ebe7cb7a"}, "address": "addr_test1qz5urft08hklx5enx8apfsl2uwrfyk82h6yfhhhkzusv6chgqq0lvpajj4j6239dv0eg8fkyphfzdh4e2ewq0px4u4ssmky45z"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58208af08d653745b206a093cc3f8dfc560277b36ca9e629a2fd310eb80fb0cbf3bc"}, "address": "addr_test1qqxzhl9ry49mcjn6az9aafwyvnfsnyhz4wf5furvd85yxkww66wl944qdle7sdt6m0ueq43u3txz9c30tt53q0ca6zfqqsdkxp"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582013a95f98b763a2913bc7e3d8a553b41cd63d37065c5b18e8b9560051aa2e0f5a"}, "address": "addr_test1qr4nrsdwl2n02tmgg7ervdjmsqteed4h9qk3klfxulx699z7hhekyju28ph48l3qszz6el3dy2euu8h9r5erzt52dkjq382zgd"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820958c3ae65c29af651bb779efcef724134b0889bc967d8a7409008cb16d0013c3"}, "address": "addr_test1qzras6eud27wnf376hhshldx0h3zwy2667ph98w3qzpmk4p88936qrse6wfdxmkdrj9ta2a2zh9pa5qd42za5830mw6q7jzx0h"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820b9bde7b6f67f5271787d7a6f0b65f3385b3c8a9ae75736374006372d60048517"}, "address": "addr_test1qqn0vcanqqz7779qyk54kcffc6zgdume5r3342693cyzzek94w4xjn6vpw4eddugs8hmw6vuw9x226a07fv3ryhefvmqffjmpv"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58203bf861d3b48380e6b1b3b6c6b5ca127fcba0b805f8755d67ca9ccb396e6aae05"}, "address": "addr_test1qr04s52teaxy5vyxdatvvyadmf3va4n2f0g4996qqwd0j0nsmc7dqrz7ajt7ug6f0t8rs374utz7w7x38q0dqqzqvpwqz92j3h"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582093ccc3b4bca81828f0f6dd7d38f02b7bdbb7c4b81426ec448b02b6d0d17fb007"}, "address": "addr_test1qzec537cq9grnv2jqgaclhdgeaw0p9qkez6j4g4ay3qpgvxxeucwgy4g26dqg6pmvwqmmmg7utrv403xgl2dqfvw9x7qd5r8g4"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820584a10e1b5736b2298611c4ecadd382b1113f4220162761a82f493d9c63966c7"}, "address": "addr_test1qrw28zy637s5nfznsz2fs5ftrh89dy8zk3see7tnnrcs5ukcuy85nuhlkml63rps7w52wjpm2fjz5msywwjwc6kffdmsucmdey"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58208062f1160a44b4e992addd08797ccc043525298a8e04834b68fb8e5d509eb9f1"}, "address": "addr_test1qplakcg2ucd92xtkn07apd96v9d8cqjj3w94k9nr9vcj0runvpfgqp2neczllccl7ymwvglh9905ryykkq6s58fuchtqayd6td"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820c4fc3936e9b1f8b076a554a99c61376a43a1105e4e946441e1e2a0fd431b5da3"}, "address": "addr_test1qpcqkm9e2n0uwdqm9jf8d5faqyrf3n7hcj8y67zkxgzzhu5f4vttf8wkx4jx7uszc75mfsralzf8wrqp2xkqq4yxg88qtfvr7l"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58209023f71c570758e01a83332a4ddef1ac3a396cbe5dc96293924178e5aa73a85e"}, "address": "addr_test1qpvrjdc7frapmlgyztlxq3mlk0mtlx4adc87eqf85fhxf2ehcvf8npp5sd596jr8myt80pz2cq28h3cam4m9w7x9m8essj5hjn"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820ad567690944c74b711f2bd6c403c0266aeb33ef569fde5e929a66ac4578940bb"}, "address": "addr_test1qr5grdfutlxekmg2jdy320u5zky8dvlrpggcup4mnjarwqann9j4q8ylmr3q7t8vppzsp6x4jspgaapwla47ehgl249srupqh8"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58203b536bb6939143c3643e55c45b19b46dfd93ccbe7d999b490b87910c67f8f5ce"}, "address": "addr_test1qq6wxgslzpzc9emduyyvwk4ptz8cqjac8tgr7n5dh9a0mng99rg3d5sznefewv9dng9ry3qyf9fqp26qnq3pma6zygns44j798"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582055a933fe1696fe8b80b80eeb9841e8e5cbc72705b898bef725f7a690f9cf2902"}, "address": "addr_test1qp0uzvrarx2m7gx7v3nt0u2afsre3zemmc3heycuawznj8z3dgxjm2ps8nswpsnktgezrfqlz4e8tz3cfgfvl0adfy6qzyvf5x"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820eb5486a25e7acb113a6b49eb311d81b8b27fd1c9295139b0fb4e1ea886e9a333"}, "address": "addr_test1qrfjufl2vuxhqyvkxxtpfw0za25c999p8zmdvqtyhv66nnn7trnwvgeqm047vaplnmzjhtejd3792rs8jc0ru5sayjcq5wnn8p"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820eb2a0425a2cbc76ab42aa99749e078ee0aedaef0f0519ff14e5fe35dbf14283e"}, "address": "addr_test1qpyj98rkr87wg9zhxyp756fecqvk4dkkwna0hdk5fwx5gknplkjcxhjqr9kc57m3p8vcdw742xrkuvudwylhg8hc5lhqegn70p"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582099e860b4e1cda64a81fe0d61efe359428a0625287067d9d6f3baaf18897a6d95"}, "address": "addr_test1qpzy9yv4kpja5szr0ar8saxg4pttckm4n250grvgpdey6xrjmupujmcefgtxgtuk4ffwj6e43lnde7dhepn7p9pv0zgq4lck4l"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820fcd30b8e9387bc932e1361767cb9338ee62583fbccdd4b968820dab092d4b50e"}, "address": "addr_test1qpelegk5t7t2j0du5lpkgkm5grpe298cqjv7wcufh2nf852etawk7dzeeuhncw9pweuamfun9cwt6ju9kruuwn0yelqqmkstrr"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58208eb7443ae318c03dc925c1c7a317ed4e308f14f5d0d78579896a344b06d8911d"}, "address": "addr_test1qq8h2294gstt78hwx3tnm68fp88q4l9v9tnwemrl6r9sygzpqdd4cfegjg0zg97z0ux9yykm9wq5wrsm878jddtp0dks3u3xfs"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820aa68ea9835b1f731fb66f86eb7129e1a78b837b172f6d93b81911f4e7dc2ec15"}, "address": "addr_test1qpq8tq56aly64xr3sptjrnm435hsaawztfxxpmpze00a3r9y6zu07qac8y33ad5hlf37s2tv4emzrykf54hpe8vsfpnsfaulzp"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582079cfa42cddca931a91aa3f33e019bf6c114064d6032d9612253e2406f836b390"}, "address": "addr_test1qzz8gpvw73ja6k8e8etlzpyu4lxzhwpk3598vg3cy7spyxuxqxnaf5p4kxzkhh67y8msaqhrg3qm2smz7nt2740stf2q44lxcy"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820ab077b26077859b02161e00747cca6fe1c305cb399fb55a7afb3b76db8ece0e3"}, "address": "addr_test1qr3u4j5uqzxxz2882r4chehkxxdlezwdestgtcgh05tjz2lyr84rk32j46cjmzn7ptqju72l33nxz7hcmyz7dwj9c44qmea74d"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820bbe285ba6305c11e8be4570ec7af912bbb7ac84d81ea013eeca7ae4422d2448d"}, "address": "addr_test1qpay2kzw0pcxlvue03c6lv52f956lwgn8fj5a3dxj7n3ncvp6kyv6pnlq289r28ks9rveewwndsyvm3lvf47k9qwhazqjjvx27"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820227fbb23046db689b1212575824f0b60ff0192594993db19356755362d1f05cd"}, "address": "addr_test1qqeyqxwde7ua0aeurtdu36zh5rys60ctwqnu2cq36ss2d9uqfd5kz3twjh6pepq8dv9r9hd2yve9j54v9ragngl93xlsjgkkpx"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820fb718b4d6d3943fdc780cc6243bc2d7458470e41e10a3dbd4b33f8856c268fa7"}, "address": "addr_test1qrt5nf8y3d7q4wz0hk5ak5n4n9zvryg2qhd6znreq8g5vppct0hx6ce86ufy7xkja5mnvjyy5e90g2pawtnsvmhdemkq7082lt"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582083b107c5f516a79f87874f1227a7f605ed118427a8718d7e8eef9f5b9e2175f1"}, "address": "addr_test1qqvz895af8s5n3glpmtppdpgh6p023upeyw5ty6cn6al3sd67tmrkhjh824s2cyer2ul66v9cpf3j7gd5g5d8ksrx48q3qvt4k"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820e72d4bc4ba8b3f996874b9233353a180f1ab96ae5ee3c163415456eb7dc9c309"}, "address": "addr_test1qrezun5rxdqn57z6ez4srgwy40m95mvd366aglyzcks9v650xndqkjavz5q3ee7l89fpfdmz6830hzjv9jhxg8kc7qeqnqh74s"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582008efb0c23cee254223e7670a26182f6fae2b85bb61be7ac2b73b4a649d83ec29"}, "address": "addr_test1qrgsjqg5nl5he4qmrm40cgapw26jey5xp36z86s4dp6y0ql8atma6d2dnqt9m3zxm6crgjj2epetcw8t54wdzlrmq8mq76ldtk"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58200064710c686465feffeb57eaadf9ed04b72b569aa6addb7a2ce0d524bda756d4"}, "address": "addr_test1qpjjlqjeqhgqh92tu4ydskkkz0qyayg747p4lhxlxpqsrnz77jz3yw4s4erdkchwf23cy7l2hyeqwflnemfazfacwt7s2q6map"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582060f70d9122bbffea0d72a6a4158b9101f69c7bbab3cfc4335588a291613ca667"}, "address": "addr_test1qzszut7ncw6trjj0aufd6f5ev5cthalf6ft6anwgtqnpach96lq6nn63lvypemq567q68n3je6quj2da44naugprkufseqlydt"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820db0bdab4fc037516b8e1892e8c22ba577943ecfccb31f3a2c24b749d95a09f69"}, "address": "addr_test1qq5288yeeyyu03c9dyyze0mwvp33acjt0dhe0uklyehhuxzlt3jq9fzd5f67jr4n7gvt8s2sul6cgwprsp5zut0srn5qnkugxe"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820d9d68af1a51c9c738a0d637f238ba14687e25e276aadbff0cd42a811659213cd"}, "address": "addr_test1qq57krgrudegs37tc9g785vqhtg7nclnafvkq7gthzx40rwxs2fgfdry0vg4jv54met9kq2nyds4s49fa04yzunqjyuspe4h0n"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582014de642b103132b51bfe8e0a63b53d10acae7eda49ffc2f2256455e3ed5852e3"}, "address": "addr_test1qphzfecrcklg7xty0daecgjsrqdvh0ul4d7az5p4l8vctcmme8avdyepm68xjz7wk9g0xn5drpyskqm7vngzf3lfugxsw4m9ta"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820a9e0dcdd0b0bd8ca64a8b192a9e3b09154a9d6f5387f5a8d6de988ac76e0ba48"}, "address": "addr_test1qq2wepd76vnslm2m7ql0lmy3secg6cxv4j64epp7adgsq55hepl7l0fzjcq0qkwe9zd4h3khggs90m4rj2ehyxdvdesqe0tkpa"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820ae2adcbd3cee1ad398a391cb1240ea7f6086dfaa2219b0cb77552814918ec134"}, "address": "addr_test1qq2sxtrzyv8grup9k3aaqsdmrmpzspywdfcpqxzrmy4pe6yd8y6q3tv3f3dly5zpneh2r7f3vq0z0r8xzqj5ldpjpj9s4mhakg"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58203f5f055a14a03098a90359a1dc3de8e9806aec7f385cac762afe2639f69c6215"}, "address": "addr_test1qzgt2pkj2w5lqw5xsdln0ysvyqksfmkdvm26pm4kte4hme2m0cd2mewx3d3q6rjjl2yrvehzxaxqur32fm77vkkxzeqqts5rzz"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582051ba4bdab4e70750bb88f3fc3175dca03e8ec84c3d5f0e082db2c66cd5f92e46"}, "address": "addr_test1qpwg8pncfyrpka3n2g094rnsesghy6x6t89jrmk5q3mvekfa93vhxrjmnxrl3hzluqw8jrpec4rr873lg0g35c0ac9rszrkqa9"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58203dc963b39006273a5de79deb47daeda8b1991684825be02e43a198c58daf1add"}, "address": "addr_test1qzgrqf8sxu2gyfggew5csn3j5yl9h8au7n5aa7pcn9ntgwnwjl4m4khdt88nnrneeguj8zvmg06r0l9775gntusa5k6su5mnf4"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58205d97c9e2bbaf10f105f102811ba3558fcde0fe830de0217ce2253cc00656ceba"}, "address": "addr_test1qrvcm5jhz6ehduydre0jnsjpx7dppjr38vdwy4f2ncywkhacpphlkrqxmtg560ercrm4uqv35ev62sg4rtw26ms7cyes2t8s4y"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820a62cd87a7c9463c30d499cc0c6e29171ec00cd0d46e695de74dfaca5b5b5050d"}, "address": "addr_test1qp5dju8pfcmqd67a4qf4lsmvqh85hhpdutxyltpg8n5syjrkx6g2q3ap5c8rf0xxh8g974tne7qqfyjee2qu69dzlw3q0kzjys"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820f77e255c72c51c28a2a4f7145a6010109201cb3083cf2d2bbaf7f80ef66faae2"}, "address": "addr_test1qqgj39e6md45dl8m9s7yaqwwcz7sr5ufpz54pzpjl3wsgwcvrkvg96r3wdtc674xexgtxqt2xdg43qq5vu4r32gznv8s245z6r"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820918c8dc3d23f0579982b389418b4d412a86dc9077461e50a9b7b1f581472461e"}, "address": "addr_test1qqed9hjwtrhn2v3h6dzr59jgddxlv3s0pn4uva9pz9kza5tvm5sjzgle30q4dkwxsy0n6u72h5x86pt5sv82d9lwwe7s9455at"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820d8b39df9090e185663517490e204aabff5aa745118c77fb772a0df71b555732f"}, "address": "addr_test1qzddjjt4f5j8qm3vqggx9qw5suryv796jxcc5nhklh90dsgq538xxj49neh2lk8kr2x3skyj9vvgj24hx6l5awu2zrdqkg38zw"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582040d166554488456a7b8a9bdd879eb52c116918954a86313636aaa02d09a768b0"}, "address": "addr_test1qzpucx5wmq07jrfj0d8cce9fwpxykvhxw8uydkmuek9zr0ctvq2j2hcvf8zrq2qws3jphtyelac49xy6c7eysrvdsmmqzpwrun"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820a3999baf0647f8a8132b21a17cb92bd7f61394c9417a0a06ab17fb4f37baa9ac"}, "address": "addr_test1qpglea3wj58kfr7t7jejrjhe6ha9q5y0kryq6xk8ucghs3xfjl8jh342ua529rkrr42r2gyattfhtv24eu5gz2cvmrqsarhh8d"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820f3016ec0282d157f3f47595dbc91d0998ec4ef714b128f5cc4a6d49a44df6af0"}, "address": "addr_test1qzx6m5wpzfu4fwjeaxtvvc3sphj4xsvhvnf5zkydfj8kcdrnzshl6ew27jxzy0t3f8d8cqfmh64c6pmguspp7lpktfpsplk4sv"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582009ad5b0e4bcfbf2f6ddcd6b41d13a6a1d073766c2b8134448fd77065684287c8"}, "address": "addr_test1qq4sa8xxg4kts09uug8adm44vgue5h6ul45n0uka5hg63lar2pfdxfddamm6pq9sqz2qy40lwf49hzme3xe8klj4y46svefmpd"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582021154504b195ce79f26c1716d487ec96638c4b824c71e3c418249f4b18a5e592"}, "address": "addr_test1qztre66cmy4tfva38t9j580qxvf3v058qcdw79a3sq0pcnx3xg5ksg2068llk42rda9ds8n0j06fvnl6hgm9ae8y99zsrwve3z"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820c2cb7bf7a900cb4b20eec08f50a4eae760ee951d8a5e9c68ff0451694624db9d"}, "address": "addr_test1qpuc6jdqpp5puyzjt7qz2e3s9ln6ecfgsvwz30rqprq0sjvv9zvs6rp7sdy6qcudskwp6q6h5pvk5n9nrn6t7l35h5lss8ylwe"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58204b16b9439028ff25e235f7b2d8ad0481d08172bb0c0b23964a2acb772ff7927b"}, "address": "addr_test1qp3hywfjjyxhfs7ja7eaf52wsyptsvutq4gwmgm9xgvc0r27f2futth8ef8sz8w3m088cugu3cgraa2vvl3alrefksdqalyfgz"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582064aca5869e08afc130bdac07723f60dbe937aaf07f2651eeba886c2efc29e4aa"}, "address": "addr_test1qpu0daxkk4f4lqwx2v0vzvcx3gtavz4wzrghx62zpl5zp5ht39d56ryy0h4x847u9678fq8xx6u4yq4kx00nejkfm6wseq56uh"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58208b8851a4e0a807637ff02ab22e19de925d049e43840c36b75b3a4cb45b4491f4"}, "address": "addr_test1qqk0280rtx729tksvlah3lswmx9s4sssgctfsq2lf3fsggjm4l7xf0hks0yj0sjfw5ynchec20efktejmtqh3x0zsxwq3w356l"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582070f96c0f1f66e835097551e61c4064834dfe75480762aeca2d9bd1b86fc1ba7d"}, "address": "addr_test1qrw5edpp3dvgs99vmqta7rvxed9ej2hlayqkd8rmvvlp96dy5m97ypz6em8fwxtk9cdgxy8whvxv0v3709xvnuf0fj4qwq2vts"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820f4e0e9beb3aaa75344f31135bcc260445edada24108b9dca0b54b0de336e1f92"}, "address": "addr_test1qzc540h2xvvhe4347gasx9k3ykta2gqn277qcxy7plssz8kgm52z8rpe9s4uhhwz8kemnuawhhvw8w5axtmu0wp5c3lsshl4dn"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820658f01aafdd8c2855a9eec25a2a7b6beb3eaaa78bba61733e5229979fe4e9ac0"}, "address": "addr_test1qr6px93r5jlam5lr7huq3um55asy7dc9grautya3znx6fj6fhst6gmnc4mkhmc2gu9ffdrax7fnu2d69zf049w3d5dcsdp3c0j"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820c15b71f08481eee1419f6b4ceb7d778d3e7d1bfe8748ca08be8d9b39c8b89776"}, "address": "addr_test1qz8xlya86v60lpchn5wl806wl52jjpupywycg2dyuttjke4gdqkyfpcq38dq5ccx2pe9jg56lqw74gy597k7upfthz3q0yv6s6"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58200a8903b013478226c0323723ca68340e8b04c56b38870462c0d9ed46788a828b"}, "address": "addr_test1qp2xxse9u7yc0fzg4q08cqdaumsqklx3tf99yxpec5wlsejmptgu9uygexx0zlz6gk4e3p20zdg7tk3szk5rtk7auk9qx2el88"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820d2f4a534f7a5b6f8e53e169a5a6c515a66a7ca9da0f33c748bd31147cca877df"}, "address": "addr_test1qqnem7ma2hd5zemysmd56mrqs5wfv7ygpjzwe02prgwwntn7mcdz7tmy0eyv2swaavmps5gwytsekmzl890favp6th3qgstzjy"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820748eba6fb1929fdd437eddd43871c4525a78996c7dd68a2093a2d4a1dcd42d5e"}, "address": "addr_test1qzj9qnd8x03ngsv29v6hcdxwmkprfkx929zkhh5j92gf6qza2x7nfsyk2p5ul3vjnsmcvlr86vqse07jp6kgn6n64a4qgrz78n"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820d5c398a03ac485050d1f5efef92cb04294b90950fa8fa0de2a63d6730be3a43e"}, "address": "addr_test1qqsrjsvga9ctmw06y3z4hn4hrzp2zqaa2xdnl5tpr9dmjamn8yylvtknlr9pt398x5hkff3pqekkhkvgmfatfpmypdgqfz8zxm"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58207045c6e00bc31243e086c67731debfbbb7d749a41a64345b953d8283649a16fc"}, "address": "addr_test1qqfj0armf9nvh2sl0zr8ujx534h3ukngpx3ewkldg4ea6mp95xrwkvsm64j0tsr63hresvu6n0e2a67s92edxntah2uq4uz5kp"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820df37410977298dc6c7d25920d9ce0f03e872c36cda3bc3ca4d4e50aaa17e72bf"}, "address": "addr_test1qq9cka5g8nh6qv5tt3ngd0k0em89ve8h36vlyuakkyw8e73ugrvcqzv93v5xszyh7xdeujnk5hv9gvhleazku7mqxq3smr5c68"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820b2a71fb7f7c8767d3fc38318fb0cccfac2c7b6577c26dd427a2b2d9bfdf8e604"}, "address": "addr_test1qzt2hsddymfe7svypf3tnepgrhtmw5c9dqshx0kdty4pc90ngr5tuyz5dl79qwnn98kjnsm02s47886e0zyfpchruyyqstfl8f"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582001db82f567f769eebef32ee9bd81b3c8c16630a46ae4b106f4c7447f7441786f"}, "address": "addr_test1qqv23gu9s4fpjaxzxvjnqqcdzy3ugf4cn29d8tg570shfxckpl9vtp3s9kyh8a7m2m3gwuw445t62ya36p0suaypavlqtw38uf"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582035a8819c5fd816504c7679c7d4e0b3c5b12e765eacc6eae0b535b4aed33dd465"}, "address": "addr_test1qrd5dkpv9u54sm994raskcq4p3vy43h0e3hv58mvjeskszt3fa3ce2wsaq57dyhl02nr836u7l65sg4u22na4zkwtq9smfx9ks"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582055f57e773105c3910c309fae354f7cf49399fc099dff8280ca768ea265f321e6"}, "address": "addr_test1qqh97jsyx6e3h2ryj8cp5ftuy2pc2udfury08fhzwduvlgd6gxl5u4y4a5kxm7455axv2sm050e5mxezdnjx72sntxdq29nkme"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58204c365ba833942147100e2987c12fdf1cb4da566452245cf9e4f922c4e8d1ad26"}, "address": "addr_test1qpyyrsqfpfvs3j4uhnlgptmsz39raakw5j7s7npzp9nrxhq08d2jy8d5cpzhq8ung9zsy529elpc4am09auuc404z6hsupgld7"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820e9376890bb088e1e935d53d65f18f8faa75bfb79803e27a1a650135988230097"}, "address": "addr_test1qz2r9mu9y357uk6mhkswk54m3cz5le6chrytvv5jhxau4v5zffpncl3pamnzmk2a28ndcep7ad2x88xeudw0z4ua5xzsa6t5ay"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58207c233befb95bc5067a6c43626d07182d80cd1aad8127aed991fa00f79f0b6cec"}, "address": "addr_test1qqch4fw78p2wkce8kppmhehakvt75vxjlky65wlkx8seyvwu3zkkh646cvapf683vt0q4uhhncxwl0egswfypsfjfzlq5jmpjr"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820e432d6c88e281bf9d3bc178685bb799b1ff57527ba452620343463a321219e8a"}, "address": "addr_test1qpln286ymus9qu677me28vmhs2qezszk92jcnrhu9tp6arkyuzrdpy690jh9kxkx2wn0teqce07npjysajpzpns05xqsn5rer2"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820d09b3df5dfe773cbca11cfb4c5c46abcd29b95b6a4cf5ae6fee35a5ffb4bd1b2"}, "address": "addr_test1qqhvg099hufuzqmlf0h3mk08u4cf9v4yxmgs33lq0d7htu4wp24xh2e5z6t00sq5f6n2xk284v2qjj3uyuuk9mremxpsqc2tc5"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820e2085aa055bb690ddd69f4bca8016e83542671196e87d3c2ec72991b50141fc8"}, "address": "addr_test1qrz3ny4cedtrqgav3kteh2kzl5zegqyzpqw8whxu3ae5dw3z47r0xr7de68ruf5ml4kgz3a4r6ud5f75z5re4hlvzlpsge64tm"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820325706b6e17b3d2d2f829c0aa7ffd0bfd36516ef4f3cf594e5f8731d9846e1a4"}, "address": "addr_test1qp062p78ygykumn0z0ahadfgrudwgphzwhx5c43r8dppa6axv6t079zsgur9jca5569ju2mlvvhyzsscjfgnpg9829pskjs8nw"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582032c843a12240d13196b5d55ac9060dc8ec5c45b0f10af8f93f462106dab772dd"}, "address": "addr_test1qr8e7ur4fhyk82z2uy3m29wpdk0ce99q39rk594h4c8z3suvpu3q7hekae3uvpvteqwwswqz6vqz52str3g55r0l8acsz7sgf5"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820c5ec97c726432f4fc7d03d063c1c0c1c5c46145ab6ee176dd17a4c57723a0263"}, "address": "addr_test1qpysucqwdh0vruz4mmk7r46jhc74yf9zgempapmq7t0k99ugy3pd786mmksclcqwtjp6khnqmsprx67ula3n9ll429xq3n837v"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820c79cb3dfde7fda9747f067c56dbaa175f04788478f74b324c6537a638c158535"}, "address": "addr_test1qzqntczre92n7ls7slw6hqdsqcsznrvm8f0v6wk9e5urvqpasld8cv8n8r0m5z7uwd2zxml83fszf0e0q8py2zjw4ppsmvjv6a"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820203e9ca1f5c01bf639d0d213a88da67df87be9aa51fad637fd2ac9466489df03"}, "address": "addr_test1qzstlc6223c833evt0u859pl6y3y427aprqgzthlvjh36za9242rttc7st3rtvny784pge9test9703fs8chf34y4jksp6u825"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820cfccbd81c30d126b41fab9a0fa93ea11d8693bda0b4fae1fd21c61792a7ad44e"}, "address": "addr_test1qqwj54jdqn5a9xcxrp989qfpsuyrpvkh03mh8363tlwwqzm89znufs474n6lyhn5nfnlp4mtvff3ua2mvg87f52gvpeqazmwh7"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582032194ee0832c1a8ada6e1c1f6c9ab50bd99aeb0797269994761bd6564fe47587"}, "address": "addr_test1qzt5lp69ecndampnuvvtvcnayh9j8a3cqphe7fu986gfnjvn2ryncytd7dtyxhug8eu2hp0mmj39h6zr5syz09268p6sev935r"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820aa4fbdd2fac33ccb890254275ae1ca1926e2569c9fa6f64a782182a8c8f8984d"}, "address": "addr_test1qqmh0xzy2cgy8swvg7yynt3ghxhhgqjf45agms3mhusxjrxmrxzuexd6wl0c38jyka3weg0pwfe0593a68ad0h3l6xpswzm78v"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58209086ca689ee57c3adb4d7bf6fcacd7c60237bb7b925c0d7d33a8e32b5fce858f"}, "address": "addr_test1qr4sx93tm3t930lzp9jyu6yq836rl89fp5dp0zpq707t9ftw0ftqrp3aa60c04l3t3raneagavk8g63j3y7c6cgqn0rqjsdft2"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58207687a37cb998a6ef6660560f6c2a08e2e362952cfdb92ff909252fe64fb6818f"}, "address": "addr_test1qqt08k40mf935y8gsa2qww8fp6rqq4xw39ahdpydlvlra9x4fks6fwxg7fyqgf8638vk7fjy2dlknvfj99yc2ed424mqy225qw"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58201f49e375c440373c43ff6d97e78886508561c36780cbf69aeb5548726b24656a"}, "address": "addr_test1qqpd46mpzgg2r5pd7vummtja4tasnce4vls5874n0wz5uvntgw7e47798xg5s6fqk3tx2wfszrm02jrtytwek92d44rsryv578"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58208e06736a19a49be41d0567628bb6918c48801e6aafaf90dc3880ecef7f4bbc21"}, "address": "addr_test1qz30ckh2m5fyxhjrxc40dtjr5w0sk2fehtfvfkwrkh8gzjlps772z664fan2kjhmtvtepryeendhnz2wh549ng9e8g4qh3vmfl"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820663c75b93138abef03aec209a887a635e1b324cdc253e00ef61fbacc3a1c6bf8"}, "address": "addr_test1qp3rpg4klp4t95tpzzf495yg7lxnehrxljw3qrd5thzehwu5kdqttj400uwkthyhjy62fqhksuvy7dcenhkf3x83825qwnkmde"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582069f95ed1cd495d87dced3deda7b534833fa1bf4095a4d10cb7b73f67a5c1d281"}, "address": "addr_test1qq3s9mejjepwdnguu0r0gflvjvvcqrlr4lfkuplg8ad2j2cjhl4ang6khtt798wyzy5htsxqmt68fyrj2ffy7w6yt3xq6wkqzq"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820e41830b1774b12952ff1ca7a210ed0ced58b44fc4f727ed907c50df522d7c725"}, "address": "addr_test1qqx34xfamcj8cp2xv8t889nwksd6htk7ly6ufgdughm52mknmv4ur49c6f2edt070qzmgjm0f9k89wza39c9jd7hk2hq448pyl"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582066fa28ed6153583c292b4466b546d499cbb672ee2ad20623d354c70a7b88ff51"}, "address": "addr_test1qq58ju8a370q7n4ulxujrssk6c98eszuumuvxyy8vc76nl7zywanfjzcp2mad75jm55esz8waulq7thz90ps3x30txks3duz5v"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582018dd4bb9f015554f4c46df02dfa4683fd49f20497151faaa19a93fd4b9f570b8"}, "address": "addr_test1qzjtr80cvne0yx8vuzvrw2l4cha0kzqhhfzxujxhkfrrwd8pad76hjgvxwjswxnaee6kwvu80f4ug6n7ly69lzl7e74sdasyqc"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58201539b8bb6a1a4fd7390cf4eb576ff7338dea6a7595ed3d9121f4ea82a1696275"}, "address": "addr_test1qremwlucnak007zx57gmqm6yxcm3yy7gf6dqlw560djvk9nfln0uprtncqwkry7epvyw63mn7zm5neeulq5wfgreyj7s8xh7x8"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582050469db716b7d5a38373f6b300a676d7fc55f2afb0e9948271c937a00c355905"}, "address": "addr_test1qrs0vfk8fwzfsdzksx4ywjnre45r54g9peafk5p792ehs9mgtsj95je6vfqnw4a4sdjdff8t4d4c4dk7fh3tjg9ldmqs4k7h44"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58205af4b5a5b29878c0a91f7a66b9d420dfd450ff3cec6142b975de99c2265eb71c"}, "address": "addr_test1qz43u6sh4d3hjh236q6as4cxs6jer8r38kyp62376c79uy9nlnhgv6jgl98huhpk43zekc4unsmd494sefl4pka0hleqnl4l2a"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820526f9ce860319985a47b696a57943cf98d6c4cf187f0f66f5cf3b8c671507a07"}, "address": "addr_test1qrm6h58kx02tdr73vjl0eg0dqqvwtgecxwkeefgrt6qznr473yv3x2kutgfk9ywrezspgzz4ed4dzg52f78qz2z0zu4s7tmdsh"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820e6648a1c07f763f85e0c871794da73dd4f29df60e6716038e9fe0ca24a8b767e"}, "address": "addr_test1qryv7sl3rdrfsu0ls0r5dmzmy9c7v76hcuha72wexjg5pxkdf49eglluc3ft9v6fnxk7pnheplxm9k299eqycj6wmzgq39yp4u"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58209ddcc8ba672d9f655bb1ef6f09ef1ee5ad1771fe818821fb6bee45fb1a73d55f"}, "address": "addr_test1qptydp0la6hfpr7f3dye47nsvulh3p26at7rnw04tacwnqpuhlrdly8cr8jsc93h3l43caql9jhp709wdzq65pgj4ths8wku4q"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58200b0d49a4bb4abf9aa811eb09b034c107d6df0e4b220aad55a9ed52c174586377"}, "address": "addr_test1qpyjhtp5y3hlfg5dl87ks809w5gtthzv79x066ynfax233w95xlrrmtq943mma9nmmkh00j6z9jxlera0haz7c27864q33uj55"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820d5165e766d21c90f29407b15b15318e21dc0894800f8e54cc115f8433555e0ce"}, "address": "addr_test1qqskuewyq2hslehw60jam988au60ddljvwv2wgkf77h4ufvkcfqfz9wv3spgjjz6wcynsfs79jzxdufkmy23v9qvluvqvzmukq"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58208635957b0163e296f6c09f0261c464af9de08b8597b5d6f7d84917103044e7af"}, "address": "addr_test1qpzredcujhqnm2en3yn70cykpqs97sqecwzysrz3nxedkaxur3nak5j59tl9ammjk7c50zmquecu8ea8kt7x29s3ndkqsm85h4"}] \ No newline at end of file +[{"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58200a2e6f6f040636a56b61276487459b9e9f309d09d1d57319b41ed32f3053af8d"}, "address": "addr_test1qzc97zml9xzhm7xcsmqretkk7ztzyehj3dpd7ph7h0s40wp0ea9f3e353pmmr7yxv7m2dj09rn44m7pvd0m4cylusn8szlm75t"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58205a06b43ec9c2b0569e7ab12d34d54238b08dad06c46b37acc4cb560a41dd1b0a"}, "address": "addr_test1qpar26cedvu2adsseslkvedd8p0spdyah9xugr956mrczj2lhcns9veuwczx5zymn3mwfzz43zetmx6rxc9lyxuquvpq5c283g"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58204e0d6d81b89cca3fc8d7a34c0e6e034154b7bfeaa63c20d8fb87a104ebe7cb7a"}, "address": "addr_test1qz5urft08hklx5enx8apfsl2uwrfyk82h6yfhhhkzusv6chgqq0lvpajj4j6239dv0eg8fkyphfzdh4e2ewq0px4u4ssmky45z"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58208af08d653745b206a093cc3f8dfc560277b36ca9e629a2fd310eb80fb0cbf3bc"}, "address": "addr_test1qqxzhl9ry49mcjn6az9aafwyvnfsnyhz4wf5furvd85yxkww66wl944qdle7sdt6m0ueq43u3txz9c30tt53q0ca6zfqqsdkxp"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582013a95f98b763a2913bc7e3d8a553b41cd63d37065c5b18e8b9560051aa2e0f5a"}, "address": "addr_test1qr4nrsdwl2n02tmgg7ervdjmsqteed4h9qk3klfxulx699z7hhekyju28ph48l3qszz6el3dy2euu8h9r5erzt52dkjq382zgd"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820958c3ae65c29af651bb779efcef724134b0889bc967d8a7409008cb16d0013c3"}, "address": "addr_test1qzras6eud27wnf376hhshldx0h3zwy2667ph98w3qzpmk4p88936qrse6wfdxmkdrj9ta2a2zh9pa5qd42za5830mw6q7jzx0h"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820b9bde7b6f67f5271787d7a6f0b65f3385b3c8a9ae75736374006372d60048517"}, "address": "addr_test1qqn0vcanqqz7779qyk54kcffc6zgdume5r3342693cyzzek94w4xjn6vpw4eddugs8hmw6vuw9x226a07fv3ryhefvmqffjmpv"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58203bf861d3b48380e6b1b3b6c6b5ca127fcba0b805f8755d67ca9ccb396e6aae05"}, "address": "addr_test1qr04s52teaxy5vyxdatvvyadmf3va4n2f0g4996qqwd0j0nsmc7dqrz7ajt7ug6f0t8rs374utz7w7x38q0dqqzqvpwqz92j3h"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582093ccc3b4bca81828f0f6dd7d38f02b7bdbb7c4b81426ec448b02b6d0d17fb007"}, "address": "addr_test1qzec537cq9grnv2jqgaclhdgeaw0p9qkez6j4g4ay3qpgvxxeucwgy4g26dqg6pmvwqmmmg7utrv403xgl2dqfvw9x7qd5r8g4"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820584a10e1b5736b2298611c4ecadd382b1113f4220162761a82f493d9c63966c7"}, "address": "addr_test1qrw28zy637s5nfznsz2fs5ftrh89dy8zk3see7tnnrcs5ukcuy85nuhlkml63rps7w52wjpm2fjz5msywwjwc6kffdmsucmdey"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58208062f1160a44b4e992addd08797ccc043525298a8e04834b68fb8e5d509eb9f1"}, "address": "addr_test1qplakcg2ucd92xtkn07apd96v9d8cqjj3w94k9nr9vcj0runvpfgqp2neczllccl7ymwvglh9905ryykkq6s58fuchtqayd6td"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820c4fc3936e9b1f8b076a554a99c61376a43a1105e4e946441e1e2a0fd431b5da3"}, "address": "addr_test1qpcqkm9e2n0uwdqm9jf8d5faqyrf3n7hcj8y67zkxgzzhu5f4vttf8wkx4jx7uszc75mfsralzf8wrqp2xkqq4yxg88qtfvr7l"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58209023f71c570758e01a83332a4ddef1ac3a396cbe5dc96293924178e5aa73a85e"}, "address": "addr_test1qpvrjdc7frapmlgyztlxq3mlk0mtlx4adc87eqf85fhxf2ehcvf8npp5sd596jr8myt80pz2cq28h3cam4m9w7x9m8essj5hjn"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820ad567690944c74b711f2bd6c403c0266aeb33ef569fde5e929a66ac4578940bb"}, "address": "addr_test1qr5grdfutlxekmg2jdy320u5zky8dvlrpggcup4mnjarwqann9j4q8ylmr3q7t8vppzsp6x4jspgaapwla47ehgl249srupqh8"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58203b536bb6939143c3643e55c45b19b46dfd93ccbe7d999b490b87910c67f8f5ce"}, "address": "addr_test1qq6wxgslzpzc9emduyyvwk4ptz8cqjac8tgr7n5dh9a0mng99rg3d5sznefewv9dng9ry3qyf9fqp26qnq3pma6zygns44j798"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582055a933fe1696fe8b80b80eeb9841e8e5cbc72705b898bef725f7a690f9cf2902"}, "address": "addr_test1qp0uzvrarx2m7gx7v3nt0u2afsre3zemmc3heycuawznj8z3dgxjm2ps8nswpsnktgezrfqlz4e8tz3cfgfvl0adfy6qzyvf5x"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820eb5486a25e7acb113a6b49eb311d81b8b27fd1c9295139b0fb4e1ea886e9a333"}, "address": "addr_test1qrfjufl2vuxhqyvkxxtpfw0za25c999p8zmdvqtyhv66nnn7trnwvgeqm047vaplnmzjhtejd3792rs8jc0ru5sayjcq5wnn8p"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820eb2a0425a2cbc76ab42aa99749e078ee0aedaef0f0519ff14e5fe35dbf14283e"}, "address": "addr_test1qpyj98rkr87wg9zhxyp756fecqvk4dkkwna0hdk5fwx5gknplkjcxhjqr9kc57m3p8vcdw742xrkuvudwylhg8hc5lhqegn70p"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582099e860b4e1cda64a81fe0d61efe359428a0625287067d9d6f3baaf18897a6d95"}, "address": "addr_test1qpzy9yv4kpja5szr0ar8saxg4pttckm4n250grvgpdey6xrjmupujmcefgtxgtuk4ffwj6e43lnde7dhepn7p9pv0zgq4lck4l"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820fcd30b8e9387bc932e1361767cb9338ee62583fbccdd4b968820dab092d4b50e"}, "address": "addr_test1qpelegk5t7t2j0du5lpkgkm5grpe298cqjv7wcufh2nf852etawk7dzeeuhncw9pweuamfun9cwt6ju9kruuwn0yelqqmkstrr"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58208eb7443ae318c03dc925c1c7a317ed4e308f14f5d0d78579896a344b06d8911d"}, "address": "addr_test1qq8h2294gstt78hwx3tnm68fp88q4l9v9tnwemrl6r9sygzpqdd4cfegjg0zg97z0ux9yykm9wq5wrsm878jddtp0dks3u3xfs"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820aa68ea9835b1f731fb66f86eb7129e1a78b837b172f6d93b81911f4e7dc2ec15"}, "address": "addr_test1qpq8tq56aly64xr3sptjrnm435hsaawztfxxpmpze00a3r9y6zu07qac8y33ad5hlf37s2tv4emzrykf54hpe8vsfpnsfaulzp"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582079cfa42cddca931a91aa3f33e019bf6c114064d6032d9612253e2406f836b390"}, "address": "addr_test1qzz8gpvw73ja6k8e8etlzpyu4lxzhwpk3598vg3cy7spyxuxqxnaf5p4kxzkhh67y8msaqhrg3qm2smz7nt2740stf2q44lxcy"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820ab077b26077859b02161e00747cca6fe1c305cb399fb55a7afb3b76db8ece0e3"}, "address": "addr_test1qr3u4j5uqzxxz2882r4chehkxxdlezwdestgtcgh05tjz2lyr84rk32j46cjmzn7ptqju72l33nxz7hcmyz7dwj9c44qmea74d"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820bbe285ba6305c11e8be4570ec7af912bbb7ac84d81ea013eeca7ae4422d2448d"}, "address": "addr_test1qpay2kzw0pcxlvue03c6lv52f956lwgn8fj5a3dxj7n3ncvp6kyv6pnlq289r28ks9rveewwndsyvm3lvf47k9qwhazqjjvx27"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820227fbb23046db689b1212575824f0b60ff0192594993db19356755362d1f05cd"}, "address": "addr_test1qqeyqxwde7ua0aeurtdu36zh5rys60ctwqnu2cq36ss2d9uqfd5kz3twjh6pepq8dv9r9hd2yve9j54v9ragngl93xlsjgkkpx"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820fb718b4d6d3943fdc780cc6243bc2d7458470e41e10a3dbd4b33f8856c268fa7"}, "address": "addr_test1qrt5nf8y3d7q4wz0hk5ak5n4n9zvryg2qhd6znreq8g5vppct0hx6ce86ufy7xkja5mnvjyy5e90g2pawtnsvmhdemkq7082lt"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582083b107c5f516a79f87874f1227a7f605ed118427a8718d7e8eef9f5b9e2175f1"}, "address": "addr_test1qqvz895af8s5n3glpmtppdpgh6p023upeyw5ty6cn6al3sd67tmrkhjh824s2cyer2ul66v9cpf3j7gd5g5d8ksrx48q3qvt4k"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820e72d4bc4ba8b3f996874b9233353a180f1ab96ae5ee3c163415456eb7dc9c309"}, "address": "addr_test1qrezun5rxdqn57z6ez4srgwy40m95mvd366aglyzcks9v650xndqkjavz5q3ee7l89fpfdmz6830hzjv9jhxg8kc7qeqnqh74s"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582008efb0c23cee254223e7670a26182f6fae2b85bb61be7ac2b73b4a649d83ec29"}, "address": "addr_test1qrgsjqg5nl5he4qmrm40cgapw26jey5xp36z86s4dp6y0ql8atma6d2dnqt9m3zxm6crgjj2epetcw8t54wdzlrmq8mq76ldtk"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58200064710c686465feffeb57eaadf9ed04b72b569aa6addb7a2ce0d524bda756d4"}, "address": "addr_test1qpjjlqjeqhgqh92tu4ydskkkz0qyayg747p4lhxlxpqsrnz77jz3yw4s4erdkchwf23cy7l2hyeqwflnemfazfacwt7s2q6map"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582060f70d9122bbffea0d72a6a4158b9101f69c7bbab3cfc4335588a291613ca667"}, "address": "addr_test1qzszut7ncw6trjj0aufd6f5ev5cthalf6ft6anwgtqnpach96lq6nn63lvypemq567q68n3je6quj2da44naugprkufseqlydt"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820db0bdab4fc037516b8e1892e8c22ba577943ecfccb31f3a2c24b749d95a09f69"}, "address": "addr_test1qq5288yeeyyu03c9dyyze0mwvp33acjt0dhe0uklyehhuxzlt3jq9fzd5f67jr4n7gvt8s2sul6cgwprsp5zut0srn5qnkugxe"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820d9d68af1a51c9c738a0d637f238ba14687e25e276aadbff0cd42a811659213cd"}, "address": "addr_test1qq57krgrudegs37tc9g785vqhtg7nclnafvkq7gthzx40rwxs2fgfdry0vg4jv54met9kq2nyds4s49fa04yzunqjyuspe4h0n"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582014de642b103132b51bfe8e0a63b53d10acae7eda49ffc2f2256455e3ed5852e3"}, "address": "addr_test1qphzfecrcklg7xty0daecgjsrqdvh0ul4d7az5p4l8vctcmme8avdyepm68xjz7wk9g0xn5drpyskqm7vngzf3lfugxsw4m9ta"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820a9e0dcdd0b0bd8ca64a8b192a9e3b09154a9d6f5387f5a8d6de988ac76e0ba48"}, "address": "addr_test1qq2wepd76vnslm2m7ql0lmy3secg6cxv4j64epp7adgsq55hepl7l0fzjcq0qkwe9zd4h3khggs90m4rj2ehyxdvdesqe0tkpa"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820ae2adcbd3cee1ad398a391cb1240ea7f6086dfaa2219b0cb77552814918ec134"}, "address": "addr_test1qq2sxtrzyv8grup9k3aaqsdmrmpzspywdfcpqxzrmy4pe6yd8y6q3tv3f3dly5zpneh2r7f3vq0z0r8xzqj5ldpjpj9s4mhakg"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58203f5f055a14a03098a90359a1dc3de8e9806aec7f385cac762afe2639f69c6215"}, "address": "addr_test1qzgt2pkj2w5lqw5xsdln0ysvyqksfmkdvm26pm4kte4hme2m0cd2mewx3d3q6rjjl2yrvehzxaxqur32fm77vkkxzeqqts5rzz"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582051ba4bdab4e70750bb88f3fc3175dca03e8ec84c3d5f0e082db2c66cd5f92e46"}, "address": "addr_test1qpwg8pncfyrpka3n2g094rnsesghy6x6t89jrmk5q3mvekfa93vhxrjmnxrl3hzluqw8jrpec4rr873lg0g35c0ac9rszrkqa9"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58203dc963b39006273a5de79deb47daeda8b1991684825be02e43a198c58daf1add"}, "address": "addr_test1qzgrqf8sxu2gyfggew5csn3j5yl9h8au7n5aa7pcn9ntgwnwjl4m4khdt88nnrneeguj8zvmg06r0l9775gntusa5k6su5mnf4"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58205d97c9e2bbaf10f105f102811ba3558fcde0fe830de0217ce2253cc00656ceba"}, "address": "addr_test1qrvcm5jhz6ehduydre0jnsjpx7dppjr38vdwy4f2ncywkhacpphlkrqxmtg560ercrm4uqv35ev62sg4rtw26ms7cyes2t8s4y"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820a62cd87a7c9463c30d499cc0c6e29171ec00cd0d46e695de74dfaca5b5b5050d"}, "address": "addr_test1qp5dju8pfcmqd67a4qf4lsmvqh85hhpdutxyltpg8n5syjrkx6g2q3ap5c8rf0xxh8g974tne7qqfyjee2qu69dzlw3q0kzjys"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820f77e255c72c51c28a2a4f7145a6010109201cb3083cf2d2bbaf7f80ef66faae2"}, "address": "addr_test1qqgj39e6md45dl8m9s7yaqwwcz7sr5ufpz54pzpjl3wsgwcvrkvg96r3wdtc674xexgtxqt2xdg43qq5vu4r32gznv8s245z6r"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820918c8dc3d23f0579982b389418b4d412a86dc9077461e50a9b7b1f581472461e"}, "address": "addr_test1qqed9hjwtrhn2v3h6dzr59jgddxlv3s0pn4uva9pz9kza5tvm5sjzgle30q4dkwxsy0n6u72h5x86pt5sv82d9lwwe7s9455at"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820d8b39df9090e185663517490e204aabff5aa745118c77fb772a0df71b555732f"}, "address": "addr_test1qzddjjt4f5j8qm3vqggx9qw5suryv796jxcc5nhklh90dsgq538xxj49neh2lk8kr2x3skyj9vvgj24hx6l5awu2zrdqkg38zw"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582040d166554488456a7b8a9bdd879eb52c116918954a86313636aaa02d09a768b0"}, "address": "addr_test1qzpucx5wmq07jrfj0d8cce9fwpxykvhxw8uydkmuek9zr0ctvq2j2hcvf8zrq2qws3jphtyelac49xy6c7eysrvdsmmqzpwrun"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820a3999baf0647f8a8132b21a17cb92bd7f61394c9417a0a06ab17fb4f37baa9ac"}, "address": "addr_test1qpglea3wj58kfr7t7jejrjhe6ha9q5y0kryq6xk8ucghs3xfjl8jh342ua529rkrr42r2gyattfhtv24eu5gz2cvmrqsarhh8d"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820f3016ec0282d157f3f47595dbc91d0998ec4ef714b128f5cc4a6d49a44df6af0"}, "address": "addr_test1qzx6m5wpzfu4fwjeaxtvvc3sphj4xsvhvnf5zkydfj8kcdrnzshl6ew27jxzy0t3f8d8cqfmh64c6pmguspp7lpktfpsplk4sv"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582009ad5b0e4bcfbf2f6ddcd6b41d13a6a1d073766c2b8134448fd77065684287c8"}, "address": "addr_test1qq4sa8xxg4kts09uug8adm44vgue5h6ul45n0uka5hg63lar2pfdxfddamm6pq9sqz2qy40lwf49hzme3xe8klj4y46svefmpd"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582021154504b195ce79f26c1716d487ec96638c4b824c71e3c418249f4b18a5e592"}, "address": "addr_test1qztre66cmy4tfva38t9j580qxvf3v058qcdw79a3sq0pcnx3xg5ksg2068llk42rda9ds8n0j06fvnl6hgm9ae8y99zsrwve3z"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820c2cb7bf7a900cb4b20eec08f50a4eae760ee951d8a5e9c68ff0451694624db9d"}, "address": "addr_test1qpuc6jdqpp5puyzjt7qz2e3s9ln6ecfgsvwz30rqprq0sjvv9zvs6rp7sdy6qcudskwp6q6h5pvk5n9nrn6t7l35h5lss8ylwe"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58204b16b9439028ff25e235f7b2d8ad0481d08172bb0c0b23964a2acb772ff7927b"}, "address": "addr_test1qp3hywfjjyxhfs7ja7eaf52wsyptsvutq4gwmgm9xgvc0r27f2futth8ef8sz8w3m088cugu3cgraa2vvl3alrefksdqalyfgz"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582064aca5869e08afc130bdac07723f60dbe937aaf07f2651eeba886c2efc29e4aa"}, "address": "addr_test1qpu0daxkk4f4lqwx2v0vzvcx3gtavz4wzrghx62zpl5zp5ht39d56ryy0h4x847u9678fq8xx6u4yq4kx00nejkfm6wseq56uh"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58208b8851a4e0a807637ff02ab22e19de925d049e43840c36b75b3a4cb45b4491f4"}, "address": "addr_test1qqk0280rtx729tksvlah3lswmx9s4sssgctfsq2lf3fsggjm4l7xf0hks0yj0sjfw5ynchec20efktejmtqh3x0zsxwq3w356l"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582070f96c0f1f66e835097551e61c4064834dfe75480762aeca2d9bd1b86fc1ba7d"}, "address": "addr_test1qrw5edpp3dvgs99vmqta7rvxed9ej2hlayqkd8rmvvlp96dy5m97ypz6em8fwxtk9cdgxy8whvxv0v3709xvnuf0fj4qwq2vts"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820f4e0e9beb3aaa75344f31135bcc260445edada24108b9dca0b54b0de336e1f92"}, "address": "addr_test1qzc540h2xvvhe4347gasx9k3ykta2gqn277qcxy7plssz8kgm52z8rpe9s4uhhwz8kemnuawhhvw8w5axtmu0wp5c3lsshl4dn"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820658f01aafdd8c2855a9eec25a2a7b6beb3eaaa78bba61733e5229979fe4e9ac0"}, "address": "addr_test1qr6px93r5jlam5lr7huq3um55asy7dc9grautya3znx6fj6fhst6gmnc4mkhmc2gu9ffdrax7fnu2d69zf049w3d5dcsdp3c0j"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820c15b71f08481eee1419f6b4ceb7d778d3e7d1bfe8748ca08be8d9b39c8b89776"}, "address": "addr_test1qz8xlya86v60lpchn5wl806wl52jjpupywycg2dyuttjke4gdqkyfpcq38dq5ccx2pe9jg56lqw74gy597k7upfthz3q0yv6s6"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58200a8903b013478226c0323723ca68340e8b04c56b38870462c0d9ed46788a828b"}, "address": "addr_test1qp2xxse9u7yc0fzg4q08cqdaumsqklx3tf99yxpec5wlsejmptgu9uygexx0zlz6gk4e3p20zdg7tk3szk5rtk7auk9qx2el88"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820d2f4a534f7a5b6f8e53e169a5a6c515a66a7ca9da0f33c748bd31147cca877df"}, "address": "addr_test1qqnem7ma2hd5zemysmd56mrqs5wfv7ygpjzwe02prgwwntn7mcdz7tmy0eyv2swaavmps5gwytsekmzl890favp6th3qgstzjy"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820748eba6fb1929fdd437eddd43871c4525a78996c7dd68a2093a2d4a1dcd42d5e"}, "address": "addr_test1qzj9qnd8x03ngsv29v6hcdxwmkprfkx929zkhh5j92gf6qza2x7nfsyk2p5ul3vjnsmcvlr86vqse07jp6kgn6n64a4qgrz78n"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820d5c398a03ac485050d1f5efef92cb04294b90950fa8fa0de2a63d6730be3a43e"}, "address": "addr_test1qqsrjsvga9ctmw06y3z4hn4hrzp2zqaa2xdnl5tpr9dmjamn8yylvtknlr9pt398x5hkff3pqekkhkvgmfatfpmypdgqfz8zxm"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58207045c6e00bc31243e086c67731debfbbb7d749a41a64345b953d8283649a16fc"}, "address": "addr_test1qqfj0armf9nvh2sl0zr8ujx534h3ukngpx3ewkldg4ea6mp95xrwkvsm64j0tsr63hresvu6n0e2a67s92edxntah2uq4uz5kp"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820df37410977298dc6c7d25920d9ce0f03e872c36cda3bc3ca4d4e50aaa17e72bf"}, "address": "addr_test1qq9cka5g8nh6qv5tt3ngd0k0em89ve8h36vlyuakkyw8e73ugrvcqzv93v5xszyh7xdeujnk5hv9gvhleazku7mqxq3smr5c68"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820b2a71fb7f7c8767d3fc38318fb0cccfac2c7b6577c26dd427a2b2d9bfdf8e604"}, "address": "addr_test1qzt2hsddymfe7svypf3tnepgrhtmw5c9dqshx0kdty4pc90ngr5tuyz5dl79qwnn98kjnsm02s47886e0zyfpchruyyqstfl8f"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582001db82f567f769eebef32ee9bd81b3c8c16630a46ae4b106f4c7447f7441786f"}, "address": "addr_test1qqv23gu9s4fpjaxzxvjnqqcdzy3ugf4cn29d8tg570shfxckpl9vtp3s9kyh8a7m2m3gwuw445t62ya36p0suaypavlqtw38uf"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582035a8819c5fd816504c7679c7d4e0b3c5b12e765eacc6eae0b535b4aed33dd465"}, "address": "addr_test1qrd5dkpv9u54sm994raskcq4p3vy43h0e3hv58mvjeskszt3fa3ce2wsaq57dyhl02nr836u7l65sg4u22na4zkwtq9smfx9ks"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582055f57e773105c3910c309fae354f7cf49399fc099dff8280ca768ea265f321e6"}, "address": "addr_test1qqh97jsyx6e3h2ryj8cp5ftuy2pc2udfury08fhzwduvlgd6gxl5u4y4a5kxm7455axv2sm050e5mxezdnjx72sntxdq29nkme"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58204c365ba833942147100e2987c12fdf1cb4da566452245cf9e4f922c4e8d1ad26"}, "address": "addr_test1qpyyrsqfpfvs3j4uhnlgptmsz39raakw5j7s7npzp9nrxhq08d2jy8d5cpzhq8ung9zsy529elpc4am09auuc404z6hsupgld7"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820e9376890bb088e1e935d53d65f18f8faa75bfb79803e27a1a650135988230097"}, "address": "addr_test1qz2r9mu9y357uk6mhkswk54m3cz5le6chrytvv5jhxau4v5zffpncl3pamnzmk2a28ndcep7ad2x88xeudw0z4ua5xzsa6t5ay"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58207c233befb95bc5067a6c43626d07182d80cd1aad8127aed991fa00f79f0b6cec"}, "address": "addr_test1qqch4fw78p2wkce8kppmhehakvt75vxjlky65wlkx8seyvwu3zkkh646cvapf683vt0q4uhhncxwl0egswfypsfjfzlq5jmpjr"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820e432d6c88e281bf9d3bc178685bb799b1ff57527ba452620343463a321219e8a"}, "address": "addr_test1qpln286ymus9qu677me28vmhs2qezszk92jcnrhu9tp6arkyuzrdpy690jh9kxkx2wn0teqce07npjysajpzpns05xqsn5rer2"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820d09b3df5dfe773cbca11cfb4c5c46abcd29b95b6a4cf5ae6fee35a5ffb4bd1b2"}, "address": "addr_test1qqhvg099hufuzqmlf0h3mk08u4cf9v4yxmgs33lq0d7htu4wp24xh2e5z6t00sq5f6n2xk284v2qjj3uyuuk9mremxpsqc2tc5"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820e2085aa055bb690ddd69f4bca8016e83542671196e87d3c2ec72991b50141fc8"}, "address": "addr_test1qrz3ny4cedtrqgav3kteh2kzl5zegqyzpqw8whxu3ae5dw3z47r0xr7de68ruf5ml4kgz3a4r6ud5f75z5re4hlvzlpsge64tm"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820325706b6e17b3d2d2f829c0aa7ffd0bfd36516ef4f3cf594e5f8731d9846e1a4"}, "address": "addr_test1qp062p78ygykumn0z0ahadfgrudwgphzwhx5c43r8dppa6axv6t079zsgur9jca5569ju2mlvvhyzsscjfgnpg9829pskjs8nw"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582032c843a12240d13196b5d55ac9060dc8ec5c45b0f10af8f93f462106dab772dd"}, "address": "addr_test1qr8e7ur4fhyk82z2uy3m29wpdk0ce99q39rk594h4c8z3suvpu3q7hekae3uvpvteqwwswqz6vqz52str3g55r0l8acsz7sgf5"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820c5ec97c726432f4fc7d03d063c1c0c1c5c46145ab6ee176dd17a4c57723a0263"}, "address": "addr_test1qpysucqwdh0vruz4mmk7r46jhc74yf9zgempapmq7t0k99ugy3pd786mmksclcqwtjp6khnqmsprx67ula3n9ll429xq3n837v"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820c79cb3dfde7fda9747f067c56dbaa175f04788478f74b324c6537a638c158535"}, "address": "addr_test1qzqntczre92n7ls7slw6hqdsqcsznrvm8f0v6wk9e5urvqpasld8cv8n8r0m5z7uwd2zxml83fszf0e0q8py2zjw4ppsmvjv6a"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820203e9ca1f5c01bf639d0d213a88da67df87be9aa51fad637fd2ac9466489df03"}, "address": "addr_test1qzstlc6223c833evt0u859pl6y3y427aprqgzthlvjh36za9242rttc7st3rtvny784pge9test9703fs8chf34y4jksp6u825"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820cfccbd81c30d126b41fab9a0fa93ea11d8693bda0b4fae1fd21c61792a7ad44e"}, "address": "addr_test1qqwj54jdqn5a9xcxrp989qfpsuyrpvkh03mh8363tlwwqzm89znufs474n6lyhn5nfnlp4mtvff3ua2mvg87f52gvpeqazmwh7"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582032194ee0832c1a8ada6e1c1f6c9ab50bd99aeb0797269994761bd6564fe47587"}, "address": "addr_test1qzt5lp69ecndampnuvvtvcnayh9j8a3cqphe7fu986gfnjvn2ryncytd7dtyxhug8eu2hp0mmj39h6zr5syz09268p6sev935r"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820aa4fbdd2fac33ccb890254275ae1ca1926e2569c9fa6f64a782182a8c8f8984d"}, "address": "addr_test1qqmh0xzy2cgy8swvg7yynt3ghxhhgqjf45agms3mhusxjrxmrxzuexd6wl0c38jyka3weg0pwfe0593a68ad0h3l6xpswzm78v"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58209086ca689ee57c3adb4d7bf6fcacd7c60237bb7b925c0d7d33a8e32b5fce858f"}, "address": "addr_test1qr4sx93tm3t930lzp9jyu6yq836rl89fp5dp0zpq707t9ftw0ftqrp3aa60c04l3t3raneagavk8g63j3y7c6cgqn0rqjsdft2"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58207687a37cb998a6ef6660560f6c2a08e2e362952cfdb92ff909252fe64fb6818f"}, "address": "addr_test1qqt08k40mf935y8gsa2qww8fp6rqq4xw39ahdpydlvlra9x4fks6fwxg7fyqgf8638vk7fjy2dlknvfj99yc2ed424mqy225qw"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58201f49e375c440373c43ff6d97e78886508561c36780cbf69aeb5548726b24656a"}, "address": "addr_test1qqpd46mpzgg2r5pd7vummtja4tasnce4vls5874n0wz5uvntgw7e47798xg5s6fqk3tx2wfszrm02jrtytwek92d44rsryv578"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58208e06736a19a49be41d0567628bb6918c48801e6aafaf90dc3880ecef7f4bbc21"}, "address": "addr_test1qz30ckh2m5fyxhjrxc40dtjr5w0sk2fehtfvfkwrkh8gzjlps772z664fan2kjhmtvtepryeendhnz2wh549ng9e8g4qh3vmfl"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820663c75b93138abef03aec209a887a635e1b324cdc253e00ef61fbacc3a1c6bf8"}, "address": "addr_test1qp3rpg4klp4t95tpzzf495yg7lxnehrxljw3qrd5thzehwu5kdqttj400uwkthyhjy62fqhksuvy7dcenhkf3x83825qwnkmde"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582069f95ed1cd495d87dced3deda7b534833fa1bf4095a4d10cb7b73f67a5c1d281"}, "address": "addr_test1qq3s9mejjepwdnguu0r0gflvjvvcqrlr4lfkuplg8ad2j2cjhl4ang6khtt798wyzy5htsxqmt68fyrj2ffy7w6yt3xq6wkqzq"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820e41830b1774b12952ff1ca7a210ed0ced58b44fc4f727ed907c50df522d7c725"}, "address": "addr_test1qqx34xfamcj8cp2xv8t889nwksd6htk7ly6ufgdughm52mknmv4ur49c6f2edt070qzmgjm0f9k89wza39c9jd7hk2hq448pyl"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582066fa28ed6153583c292b4466b546d499cbb672ee2ad20623d354c70a7b88ff51"}, "address": "addr_test1qq58ju8a370q7n4ulxujrssk6c98eszuumuvxyy8vc76nl7zywanfjzcp2mad75jm55esz8waulq7thz90ps3x30txks3duz5v"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582018dd4bb9f015554f4c46df02dfa4683fd49f20497151faaa19a93fd4b9f570b8"}, "address": "addr_test1qzjtr80cvne0yx8vuzvrw2l4cha0kzqhhfzxujxhkfrrwd8pad76hjgvxwjswxnaee6kwvu80f4ug6n7ly69lzl7e74sdasyqc"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58201539b8bb6a1a4fd7390cf4eb576ff7338dea6a7595ed3d9121f4ea82a1696275"}, "address": "addr_test1qremwlucnak007zx57gmqm6yxcm3yy7gf6dqlw560djvk9nfln0uprtncqwkry7epvyw63mn7zm5neeulq5wfgreyj7s8xh7x8"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "582050469db716b7d5a38373f6b300a676d7fc55f2afb0e9948271c937a00c355905"}, "address": "addr_test1qrs0vfk8fwzfsdzksx4ywjnre45r54g9peafk5p792ehs9mgtsj95je6vfqnw4a4sdjdff8t4d4c4dk7fh3tjg9ldmqs4k7h44"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58205af4b5a5b29878c0a91f7a66b9d420dfd450ff3cec6142b975de99c2265eb71c"}, "address": "addr_test1qz43u6sh4d3hjh236q6as4cxs6jer8r38kyp62376c79uy9nlnhgv6jgl98huhpk43zekc4unsmd494sefl4pka0hleqnl4l2a"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820526f9ce860319985a47b696a57943cf98d6c4cf187f0f66f5cf3b8c671507a07"}, "address": "addr_test1qrm6h58kx02tdr73vjl0eg0dqqvwtgecxwkeefgrt6qznr473yv3x2kutgfk9ywrezspgzz4ed4dzg52f78qz2z0zu4s7tmdsh"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820e6648a1c07f763f85e0c871794da73dd4f29df60e6716038e9fe0ca24a8b767e"}, "address": "addr_test1qryv7sl3rdrfsu0ls0r5dmzmy9c7v76hcuha72wexjg5pxkdf49eglluc3ft9v6fnxk7pnheplxm9k299eqycj6wmzgq39yp4u"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58209ddcc8ba672d9f655bb1ef6f09ef1ee5ad1771fe818821fb6bee45fb1a73d55f"}, "address": "addr_test1qptydp0la6hfpr7f3dye47nsvulh3p26at7rnw04tacwnqpuhlrdly8cr8jsc93h3l43caql9jhp709wdzq65pgj4ths8wku4q"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58200b0d49a4bb4abf9aa811eb09b034c107d6df0e4b220aad55a9ed52c174586377"}, "address": "addr_test1qpyjhtp5y3hlfg5dl87ks809w5gtthzv79x066ynfax233w95xlrrmtq943mma9nmmkh00j6z9jxlera0haz7c27864q33uj55"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "5820d5165e766d21c90f29407b15b15318e21dc0894800f8e54cc115f8433555e0ce"}, "address": "addr_test1qqskuewyq2hslehw60jam988au60ddljvwv2wgkf77h4ufvkcfqfz9wv3spgjjz6wcynsfs79jzxdufkmy23v9qvluvqvzmukq"}, {"skey": {"type": "PaymentSigningKeyShelley_ed25519", "description": "Payment Signing Key", "cborHex": "58208635957b0163e296f6c09f0261c464af9de08b8597b5d6f7d84917103044e7af"}, "address": "addr_test1qpzredcujhqnm2en3yn70cykpqs97sqecwzysrz3nxedkaxur3nak5j59tl9ammjk7c50zmquecu8ea8kt7x29s3ndkqsm85h4"}] diff --git a/gov-action-loader/frontend/.env.example b/gov-action-loader/frontend/.env.example index dff54b8f7..ca3203c85 100644 --- a/gov-action-loader/frontend/.env.example +++ b/gov-action-loader/frontend/.env.example @@ -1,3 +1,3 @@ VITE_DATA_LOADER_API=https://vva-governance.cardanoapi.io -VITE_VVA_WEBAPP_URL=https://vva-be.cardanoapi.io \ No newline at end of file +VITE_VVA_WEBAPP_URL=https://vva-be.cardanoapi.io diff --git a/gov-action-loader/frontend/.gitignore b/gov-action-loader/frontend/.gitignore index 1cac5597e..7ceb59f89 100644 --- a/gov-action-loader/frontend/.gitignore +++ b/gov-action-loader/frontend/.gitignore @@ -22,4 +22,4 @@ dist-ssr *.njsproj *.sln *.sw? -.env \ No newline at end of file +.env diff --git a/gov-action-loader/frontend/README.md b/gov-action-loader/frontend/README.md index 876636f1a..321528145 100644 --- a/gov-action-loader/frontend/README.md +++ b/gov-action-loader/frontend/README.md @@ -5,9 +5,9 @@ It contains simple forms for loading multiple/specific proposals in the sancho n ### Depends On - [gov-action-loader-be](../gov-action-loader-be/) - + ### Limitations -Gov action loader backend instance uses fixed set of wallet to perform transactions. This means that gov action loader can be used by only 1 user at a time. +Gov action loader backend instance uses fixed set of wallet to perform transactions. This means that gov action loader can be used by only 1 user at a time. ## Running the frontend diff --git a/gov-action-loader/frontend/public/vite.svg b/gov-action-loader/frontend/public/vite.svg index e7b8dfb1b..ee9fadaf9 100644 --- a/gov-action-loader/frontend/public/vite.svg +++ b/gov-action-loader/frontend/public/vite.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/gov-action-loader/frontend/src/assets/vite.svg b/gov-action-loader/frontend/src/assets/vite.svg index e7b8dfb1b..ee9fadaf9 100644 --- a/gov-action-loader/frontend/src/assets/vite.svg +++ b/gov-action-loader/frontend/src/assets/vite.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/govtool/backend/.envrc b/govtool/backend/.envrc new file mode 100644 index 000000000..28444c771 --- /dev/null +++ b/govtool/backend/.envrc @@ -0,0 +1,3 @@ +source_up_if_exists +watch_file vva-be.cabal default.nix +use flake --extra-experimental-features nix-command --extra-experimental-features flakes ../..#backend diff --git a/govtool/backend/.gitignore b/govtool/backend/.gitignore index 5dc5c47e4..148b93507 100644 --- a/govtool/backend/.gitignore +++ b/govtool/backend/.gitignore @@ -1,3 +1,3 @@ # other .vscode -dev-config.json \ No newline at end of file +dev-config.json diff --git a/govtool/backend/.stylish-haskell.yaml b/govtool/backend/.stylish-haskell.yaml new file mode 100644 index 000000000..6c806b76c --- /dev/null +++ b/govtool/backend/.stylish-haskell.yaml @@ -0,0 +1,490 @@ +# stylish-haskell configuration file +# ================================== + +# The stylish-haskell tool is mainly configured by specifying steps. These steps +# are a list, so they have an order, and one specific step may appear more than +# once (if needed). Each file is processed by these steps in the given order. +steps: + # Convert some ASCII sequences to their Unicode equivalents. This is disabled + # by default. + # - unicode_syntax: + # # In order to make this work, we also need to insert the UnicodeSyntax + # # language pragma. If this flag is set to true, we insert it when it's + # # not already present. You may want to disable it if you configure + # # language extensions using some other method than pragmas. Default: + # # true. + # add_language_pragma: true + + # Format module header + # + # Currently, this option is not configurable and will format all exports and + # module declarations to minimize diffs + # + - module_header: + # # How many spaces use for indentation in the module header. + indent: 4 + # + # # Should export lists be sorted? Sorting is only performed within the + # # export section, as delineated by Haddock comments. + sort: true + # + # # See `separate_lists` for the `imports` step. + separate_lists: true + # + # # When to break the "where". + # # Possible values: + # # - exports: only break when there is an explicit export list. + # # - single: only break when the export list counts more than one export. + # # - inline: only break when the export list is too long. This is + # # determined by the `columns` setting. Not applicable when the export + # # list contains comments as newlines will be required. + # # - always: always break before the "where". + # break_where: exports + # + # # Where to put open bracket + # # Possible values: + # # - same_line: put open bracket on the same line as the module name, before the + # # comment of the module + # # - next_line: put open bracket on the next line, after module comment + # open_bracket: next_line + + # Format record definitions. This is disabled by default. + # + # You can control the layout of record fields. The only rules that can't be configured + # are these: + # + # - "|" is always aligned with "=" + # - "," in fields is always aligned with "{" + # - "}" is likewise always aligned with "{" + # + - records: + # How to format equals sign between type constructor and data constructor. + # Possible values: + # - "same_line" -- leave "=" AND data constructor on the same line as the type constructor. + # - "indent N" -- insert a new line and N spaces from the beginning of the next line. + equals: "indent 2" + + # How to format first field of each record constructor. + # Possible values: + # - "same_line" -- "{" and first field goes on the same line as the data constructor. + # - "indent N" -- insert a new line and N spaces from the beginning of the data constructor + first_field: "indent 2" + + # How many spaces to insert between the column with "," and the beginning of the comment in the next line. + field_comment: 2 + + # How many spaces to insert before "deriving" clause. Deriving clauses are always on separate lines. + deriving: 2 + + # How many spaces to insert before "via" clause counted from indentation of deriving clause + # Possible values: + # - "same_line" -- "via" part goes on the same line as "deriving" keyword. + # - "indent N" -- insert a new line and N spaces from the beginning of "deriving" keyword. + via: "indent 2" + # + # # Sort typeclass names in the "deriving" list alphabetically. + # sort_deriving: true + # + # # Whether or not to break enums onto several lines + # # + # # Default: false + # break_enums: false + # + # # Whether or not to break single constructor data types before `=` sign + # # + # # Default: true + # break_single_constructors: true + # + # # Whether or not to curry constraints on function. + # # + # # E.g: @allValues :: Enum a => Bounded a => Proxy a -> [a]@ + # # + # # Instead of @allValues :: (Enum a, Bounded a) => Proxy a -> [a]@ + # # + # # Default: false + curried_context: false + + # Align the right hand side of some elements. This is quite conservative + # and only applies to statements where each element occupies a single + # line. + # Possible values: + # - always - Always align statements. + # - adjacent - Align statements that are on adjacent lines in groups. + # - never - Never align statements. + # All default to always. + - simple_align: + cases: always + top_level_patterns: always + records: always + multi_way_if: always + + # Import cleanup + - imports: + # There are different ways we can align names and lists. + # + # - global: Align the import names and import list throughout the entire + # file. + # + # - file: Like global, but don't add padding when there are no qualified + # imports in the file. + # + # - group: Only align the imports per group (a group is formed by adjacent + # import lines). + # + # - none: Do not perform any alignment. + # + # Default: global. + align: file + + # The following options affect only import list alignment. + # + # List align has following options: + # + # - after_alias: Import list is aligned with end of import including + # 'as' and 'hiding' keywords. + # + # > import qualified Data.List as List (concat, foldl, foldr, head, + # > init, last, length) + # + # - with_alias: Import list is aligned with start of alias or hiding. + # + # > import qualified Data.List as List (concat, foldl, foldr, head, + # > init, last, length) + # + # - with_module_name: Import list is aligned `list_padding` spaces after + # the module name. + # + # > import qualified Data.List as List (concat, foldl, foldr, head, + # init, last, length) + # + # This is mainly intended for use with `pad_module_names: false`. + # + # > import qualified Data.List as List (concat, foldl, foldr, head, + # init, last, length, scanl, scanr, take, drop, + # sort, nub) + # + # - new_line: Import list starts always on new line. + # + # > import qualified Data.List as List + # > (concat, foldl, foldr, head, init, last, length) + # + # - repeat: Repeat the module name to align the import list. + # + # > import qualified Data.List as List (concat, foldl, foldr, head) + # > import qualified Data.List as List (init, last, length) + # + # Default: after_alias + list_align: after_alias + + # Right-pad the module names to align imports in a group: + # + # - true: a little more readable + # + # > import qualified Data.List as List (concat, foldl, foldr, + # > init, last, length) + # > import qualified Data.List.Extra as List (concat, foldl, foldr, + # > init, last, length) + # + # - false: diff-safe + # + # > import qualified Data.List as List (concat, foldl, foldr, init, + # > last, length) + # > import qualified Data.List.Extra as List (concat, foldl, foldr, + # > init, last, length) + # + # Default: true + pad_module_names: true + + # Long list align style takes effect when import is too long. This is + # determined by 'columns' setting. + # + # - inline: This option will put as much specs on same line as possible. + # + # - new_line: Import list will start on new line. + # + # - new_line_multiline: Import list will start on new line when it's + # short enough to fit to single line. Otherwise it'll be multiline. + # + # - multiline: One line per import list entry. + # Type with constructor list acts like single import. + # + # > import qualified Data.Map as M + # > ( empty + # > , singleton + # > , ... + # > , delete + # > ) + # + # Default: inline + long_list_align: inline + + # Align empty list (importing instances) + # + # Empty list align has following options + # + # - inherit: inherit list_align setting + # + # - right_after: () is right after the module name: + # + # > import Vector.Instances () + # + # Default: inherit + empty_list_align: inherit + + # List padding determines indentation of import list on lines after import. + # This option affects 'long_list_align'. + # + # - : constant value + # + # - module_name: align under start of module name. + # Useful for 'file' and 'group' align settings. + # + # Default: 4 + list_padding: 4 + + # Separate lists option affects formatting of import list for type + # or class. The only difference is single space between type and list + # of constructors, selectors and class functions. + # + # - true: There is single space between Foldable type and list of it's + # functions. + # + # > import Data.Foldable (Foldable (fold, foldl, foldMap)) + # + # - false: There is no space between Foldable type and list of it's + # functions. + # + # > import Data.Foldable (Foldable(fold, foldl, foldMap)) + # + # Default: true + separate_lists: true + + # Space surround option affects formatting of import lists on a single + # line. The only difference is single space after the initial + # parenthesis and a single space before the terminal parenthesis. + # + # - true: There is single space associated with the enclosing + # parenthesis. + # + # > import Data.Foo ( foo ) + # + # - false: There is no space associated with the enclosing parenthesis + # + # > import Data.Foo (foo) + # + # Default: false + space_surround: false + + # Post qualify option moves any qualifies found in import declarations + # to the end of the declaration. This also adjust padding for any + # unqualified import declarations. + # + # - true: Qualified as is moved to the end of the + # declaration. + # + # > import Data.Bar + # > import Data.Foo qualified as F + # + # - false: Qualified remains in the default location and unqualified + # imports are padded to align with qualified imports. + # + # > import Data.Bar + # > import qualified Data.Foo as F + # + # Default: false + post_qualify: false + + # Automatically group imports based on their module names, with + # a blank line separating each group. Groups are ordered in + # alphabetical order. + # + # By default, this groups by the first part of each module's + # name (Control.* will be grouped together, Data.*... etc), but + # this can be configured with the group_patterns setting. + # + # When enabled, this rewrites existing blank lines and groups. + # + # - true: Group imports by the first part of the module name. + # + # > import Control.Applicative + # > import Control.Monad + # > import Control.Monad.MonadError + # > + # > import Data.Functor + # + # - false: Keep import groups as-is (still sorting and + # formatting the imports within each group) + # + # > import Control.Monad + # > import Data.Functor + # > + # > import Control.Applicative + # > import Control.Monad.MonadError + # + # Default: false + group_imports: true + + # A list of rules specifying how to group modules and how to + # order the groups. + # + # Each rule has a match field; the rule only applies to module + # names matched by this pattern. Patterns are POSIX extended + # regular expressions; see the documentation of Text.Regex.TDFA + # for details: + # https://hackage.haskell.org/package/regex-tdfa-1.3.1.2/docs/Text-Regex-TDFA.html + # + # Rules are processed in order, so only the *first* rule that + # matches a specific module will apply. Any module names that do + # not match a single rule will be put into a single group at the + # end of the import block. + # + # Example: group MyApp modules first, with everything else in + # one group at the end. + # + # group_rules: + # - match: "^MyApp\\>" + # + # > import MyApp + # > import MyApp.Foo + # > + # > import Control.Monad + # > import MyApps + # > import Test.MyApp + # + # A rule can also optionally have a sub_group pattern. Imports + # that match the rule will be broken up into further groups by + # the part of the module name matched by the sub_group pattern. + # + # Example: group MyApp modules first, then everything else + # sub-grouped by the first part of the module name. + # + # group_rules: + # - match: "^MyApp\\>" + # - match: "." + # sub_group: "^[^.]+" + # + # > import MyApp + # > import MyApp.Foo + # > + # > import Control.Applicative + # > import Control.Monad + # > + # > import Data.Map + # + # A pattern only needs to match part of the module name, which + # could be in the middle. You can use ^pattern to anchor to the + # beginning of the module name, pattern$ to anchor to the end + # and ^pattern$ to force a full match. Example: + # + # - "Test\\." would match "Test.Foo" and "Foo.Test.Lib" + # - "^Test\\." would match "Test.Foo" but not "Foo.Test.Lib" + # - "\\.Test$" would match "Foo.Test" but not "Foo.Test.Lib" + # - "^Test$" would *only* match "Test" + # + # You can use \\< and \\> to anchor against the beginning and + # end of words, respectively. For example: + # + # - "^Test\\." would match "Test.Foo" but not "Test" or "Tests" + # - "^Test\\>" would match "Test.Foo" and "Test", but not + # "Tests" + # + # The default is a single rule that matches everything and + # sub-groups based on the first component of the module name. + # + # Default: [{ "match" : ".*", "sub_group": "^[^.]+" }] + group_rules: + - match: ".*" + sub_group: "^[^.]+" + + # Language pragmas + - language_pragmas: + # We can generate different styles of language pragma lists. + # + # - vertical: Vertical-spaced language pragmas, one per line. + # + # - compact: A more compact style. + # + # - compact_line: Similar to compact, but wrap each line with + # `{-# LANGUAGE #-}'. + # + # - vertical_compact: Similar to vertical, but use only one language pragma. + # + # Default: vertical. + style: vertical + + # Align affects alignment of closing pragma brackets. + # + # - true: Brackets are aligned in same column. + # + # - false: Brackets are not aligned together. There is only one space + # between actual import and closing bracket. + # + # Default: true + align: true + + # stylish-haskell can detect redundancy of some language pragmas. If this + # is set to true, it will remove those redundant pragmas. Default: true. + remove_redundant: true + + # Language prefix to be used for pragma declaration, this allows you to + # use other options non case-sensitive like "language" or "Language". + # If a non correct String is provided, it will default to: LANGUAGE. + language_prefix: LANGUAGE + + # Replace tabs by spaces. This is disabled by default. + - tabs: + # Number of spaces to use for each tab. Default: 8, as specified by the + # Haskell report. + spaces: 4 + + # Remove trailing whitespace + - trailing_whitespace: {} + + # Squash multiple spaces between the left and right hand sides of some + # elements into single spaces. Basically, this undoes the effect of + # simple_align but is a bit less conservative. + # - squash: {} + +# A common setting is the number of columns (parts of) code will be wrapped +# to. Different steps take this into account. +# +# Set this to null to disable all line wrapping. +# +# Default: 80. +columns: 110 + +# By default, line endings are converted according to the OS. You can override +# preferred format here. +# +# - native: Native newline format. CRLF on Windows, LF on other OSes. +# +# - lf: Convert to LF ("\n"). +# +# - crlf: Convert to CRLF ("\r\n"). +# +# Default: native. +newline: native + +# Sometimes, language extensions are specified in a cabal file or from the +# command line instead of using language pragmas in the file. stylish-haskell +# needs to be aware of these, so it can parse the file correctly. +# +# No language extensions are enabled by default. +language_extensions: + - FlexibleContexts + - GADTs + - TemplateHaskell + - TypeApplications + - DataKinds + - RankNTypes + - LambdaCase + - ScopedTypeVariables + - PolyKinds + - TypeOperators + - TypeFamilies + +# Attempt to find the cabal file in ancestors of the current directory, and +# parse options (currently only language extensions) from that. +# +# Default: true +cabal: true diff --git a/govtool/backend/Dockerfile b/govtool/backend/Dockerfile index 75600043c..b8882627d 100644 --- a/govtool/backend/Dockerfile +++ b/govtool/backend/Dockerfile @@ -3,4 +3,4 @@ FROM 733019650473.dkr.ecr.eu-west-1.amazonaws.com/backend-base:$BASE_IMAGE_TAG WORKDIR /src COPY . . RUN cabal build -RUN cp dist-newstyle/build/x86_64-linux/ghc-9.2.8/vva-be-0.1.0.0/x/vva-be/build/vva-be/vva-be /usr/local/bin +RUN cp dist-newstyle/build/x86_64-linux/ghc-9.2.7/vva-be-0.1.0.0/x/vva-be/build/vva-be/vva-be /usr/local/bin diff --git a/govtool/backend/Dockerfile.base b/govtool/backend/Dockerfile.base index e9b93f093..8f4e4c356 100644 --- a/govtool/backend/Dockerfile.base +++ b/govtool/backend/Dockerfile.base @@ -4,7 +4,7 @@ # is a common practice in Haskell projects, as it can significantly reduce the # time it takes to build the project. -FROM haskell:9.2-buster +FROM haskell:9.2.7-buster WORKDIR /src COPY . . RUN cabal update && cabal configure && cabal install --only-dependencies && rm -rf /src/* diff --git a/govtool/backend/README.md b/govtool/backend/README.md index 4f6211a41..8d539b304 100644 --- a/govtool/backend/README.md +++ b/govtool/backend/README.md @@ -3,6 +3,7 @@ This is a backend application of GovTool project. ## Prerequisites + In order to run `backend` your host machine will need access to the `cardano-db-sync` postgres database. To have this database running locally you'll need: * `cardano-node` * `cardano-db-sync` @@ -18,29 +19,26 @@ You will need your `cardano-node` and `cardano-db-sync` to be compatible with Sa [`sancho` testnet config files](https://sancho.network/tutorials/start-node/) -### Using Nix +You can utilize the [docker-compose.node+dbsync.yml](../../scripts/govtool/docker-compose.node+dbsync.yml) file to setup the required services. + +### Using Nix and Direnv Due to problems with openapi3 package it's hard to build this project with plain `ghc` and `cabal-install`. Until the prolem is solved we reccomend using `nix` - this problem is fixed when you build your project from inside of the nix shell. 1. Get [Nix](https://nixos.org/download). -2. Enter `govtool/backend` directory: - - ```sh - cd govtool/backend - ``` +2. Get [direnv](https://direnv.net/). -3. Allow broken nix packages +3. Enter `govtool/backend` directory: ```sh - export NIXPKGS_ALLOW_BROKEN=1 + cd govtool/backend ``` - This is due to `openapi3` package being marked as broken. -4. Run `nix-shell` +4. Allow direnv to setup your environment: ```sh - nix-shell + direnv allow ``` 5. Update cabal & build project @@ -56,3 +54,21 @@ Due to problems with openapi3 package it's hard to build this project with plain ``` > [!WARNING] > In the context of our ongoing project enhancements, it is assumed that the executable previously known as 'vva-be' should be now officially renamed to 'govtool-backend'. This change is necessary for aligning with the updated branding and functional scope of the application and it has to be implemented in the near future as a chore and refactoring ticket. Make sure that the documentation matches the actual name of the executable. + +## Development + +### Linter + +In the development environment in Nix, the [`hlint`](https://github.com/ndmitchell/hlint) tool is readily available to verify Haskell files. + +By using `hlint`, developers can ensure that their code adheres to best practices and follows appropriate guidelines. By incorporating `hlint` into the development process, developers can catch potential errors and make necessary improvements early on, ultimately leading to more efficient and robust software development. + +### Formatter + +To easily format Haskell code, ensuring consistency and readability across the codebase the [`stylish-haskell`](https://github.com/haskell/stylish-haskell) formatter has been introduced into the nix configuration. + +Developers can streamline the process of formatting their code, reducing the time and effort required for manual formatting. + +### HLS + +Developers can use the IDE integrations for Language Server Protocol (LSP) by utilising the [Haskell-Language-Server](https://github.com/haskell/haskell-language-server) that include support for GHC 9.2.7. Using such integration, developers can ensure a seamless and efficient experience with Haskell code in their IDE. diff --git a/govtool/backend/app/Main.hs b/govtool/backend/app/Main.hs index 111a142ab..f4df1c03c 100644 --- a/govtool/backend/app/Main.hs +++ b/govtool/backend/app/Main.hs @@ -1,90 +1,77 @@ -{-# LANGUAGE DataKinds #-} -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE OverloadedLabels #-} -{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE NamedFieldPuns #-} +{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE TypeApplications #-} -{-# LANGUAGE TypeOperators #-} -{-# LANGUAGE OverloadedLabels #-} -{-# LANGUAGE NamedFieldPuns #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE TypeOperators #-} module Main where -import Control.Exception - ( Exception, - SomeException, - fromException, - throw, - ) -import Control.Lens.Operators ((.~)) -import Control.Monad -import Control.Monad.IO.Class -import Control.Monad.Trans.Except -import Control.Monad.Trans.Reader -import Data.Aeson hiding (Error) -import Data.ByteString.Char8 (unpack) -import qualified Data.ByteString as BS -import Data.Function ((&)) -import Data.Monoid (mempty) -import Data.OpenApi (OpenApi, Server (Server), servers, _openApiServers, _serverDescription, _serverUrl, _serverVariables) -import Data.Proxy -import Data.String (fromString) -import Data.String.Conversions - ( cs, - ) -import qualified Data.Text as Text -import qualified Data.Text.IO as Text -import qualified Data.Text.Lazy as LazyText -import qualified Data.Text.Lazy.Encoding as LazyText -import Network.Wai -import Network.Wai - ( Request, - rawPathInfo, - requestHeaderHost, - ) -import Network.Wai.Handler.Warp -import Network.Wai.Handler.Warp - ( defaultOnException, - defaultSettings, - runSettings, - setOnException, - setPort, - ) -import Network.Wai.Middleware.Cors -import Options.Applicative (execParser) -import Servant -import Servant.API.ContentTypes -import Servant.OpenApi (toOpenApi) -import qualified Servant.Server as Servant -import Servant.Swagger.UI - ( SwaggerSchemaUI, - swaggerSchemaUIServer, - ) -import System.IO (stderr) -import System.Log.Raven - ( initRaven, - register, - silentFallback, - ) -import System.Log.Raven.Transport.HttpConduit (sendRecord) -import System.Log.Raven.Types - ( SentryLevel (Error), - SentryRecord (..), - ) -import VVA.API -import VVA.CommandLine -import VVA.Config -import Data.Function ((&)) -import Control.Lens.Operators ((.~)) -import Data.Monoid (mempty) -import qualified Data.Cache as Cache -import VVA.API.Types -import System.Clock (TimeSpec(TimeSpec)) -import Data.Pool (newPool, defaultPoolConfig) -import Database.PostgreSQL.Simple (connectPostgreSQL, close) -import Data.Text.Encoding (encodeUtf8) -import Data.Has (getter) -import VVA.Types (AppError(ValidationError, NotFoundError, CriticalError), CacheEnv(..), AppEnv(..)) +import Control.Exception (Exception, + SomeException, + fromException, throw) +import Control.Lens.Operators ((.~)) +import Control.Monad +import Control.Monad.IO.Class +import Control.Monad.Trans.Except +import Control.Monad.Trans.Reader + +import Data.Aeson hiding (Error) +import qualified Data.ByteString as BS +import Data.ByteString.Char8 (unpack) +import qualified Data.Cache as Cache +import Data.Function ((&)) +import Data.Has (getter) +import Data.Monoid (mempty) +import Data.OpenApi (OpenApi, + Server (Server), + _openApiServers, + _serverDescription, + _serverUrl, + _serverVariables, + servers) +import Data.Pool (createPool) +import Data.Proxy +import Data.String (fromString) +import Data.String.Conversions (cs) +import qualified Data.Text as Text +import Data.Text.Encoding (encodeUtf8) +import qualified Data.Text.IO as Text +import qualified Data.Text.Lazy as LazyText +import qualified Data.Text.Lazy.Encoding as LazyText + +import Database.PostgreSQL.Simple (close, + connectPostgreSQL) + +import Network.Wai +import Network.Wai.Handler.Warp +import Network.Wai.Middleware.Cors + +import Options.Applicative (execParser) + +import Servant +import Servant.API.ContentTypes +import Servant.OpenApi (toOpenApi) +import qualified Servant.Server as Servant +import Servant.Swagger.UI (SwaggerSchemaUI, + swaggerSchemaUIServer) + +import System.Clock (TimeSpec (TimeSpec)) +import System.IO (stderr) +import System.Log.Raven (initRaven, register, + silentFallback) +import System.Log.Raven.Transport.HttpConduit (sendRecord) +import System.Log.Raven.Types (SentryLevel (Error), + SentryRecord (..)) + +import VVA.API +import VVA.API.Types +import VVA.CommandLine +import VVA.Config +import VVA.Types (AppEnv (..), + AppError (CriticalError, NotFoundError, ValidationError), + CacheEnv (..)) proxyAPI :: Proxy (VVAApi :<|> SwaggerAPI) proxyAPI = Proxy @@ -94,7 +81,7 @@ main = do commandLineConfig <- execParser cmdParser vvaConfig <- loadVVAConfig (clcConfigPath commandLineConfig) case clcCommand commandLineConfig of - StartApp -> startApp vvaConfig + StartApp -> startApp vvaConfig ShowConfig -> Text.putStrLn $ vvaConfigToText vvaConfig startApp :: VVAConfig -> IO () @@ -138,7 +125,7 @@ startApp vvaConfig = do , dRepListCache , networkMetricsCache } - connectionPool <- newPool $ defaultPoolConfig (connectPostgreSQL (encodeUtf8 (dbSyncConnectionString $ getter vvaConfig))) close 1 60 + connectionPool <- createPool (connectPostgreSQL (encodeUtf8 (dbSyncConnectionString $ getter vvaConfig))) close 1 1 60 let appEnv = AppEnv {vvaConfig=vvaConfig, vvaCache=cacheEnv, vvaConnectionPool=connectionPool} server' <- mkVVAServer appEnv @@ -161,6 +148,8 @@ exceptionHandler vvaConfig mRequest exception = do (formatMessage mRequest exception) (recordUpdate mRequest exception) + + formatMessage :: Maybe Request -> SomeException -> String formatMessage Nothing exception = "Exception before request could be parsed: " ++ show exception formatMessage (Just request) exception = "Exception " ++ show exception ++ " while handling request " ++ show request @@ -170,7 +159,7 @@ recordUpdate Nothing exception record = record recordUpdate (Just request) exception record = record { srCulprit = Just $ unpack $ rawPathInfo request, - srServerName = fmap unpack $ requestHeaderHost request + srServerName = unpack <$> requestHeaderHost request } shouldDisplayException :: SomeException -> Bool @@ -241,7 +230,8 @@ mkVVAServer appEnv = do (liftServer appEnv :<|> swagger) ) -newtype TextException = TextException Text.Text +newtype TextException + = TextException Text.Text instance Show TextException where show (TextException e) = show e diff --git a/govtool/backend/default.nix b/govtool/backend/default.nix index aab2212bc..575bf4dcc 100644 --- a/govtool/backend/default.nix +++ b/govtool/backend/default.nix @@ -1,22 +1,38 @@ -{ pkgs ? import {} }: +{ pkgs ? import { } }: let - # This is the version of the Haskell compiler we reccommend using. - ghcPackages = pkgs.haskell.packages.ghc927; + inherit (pkgs.lib.trivial) pipe; + inherit (pkgs) haskell; + inherit (haskell) lib; - additionalTools = drv: pkgs.haskell.lib.addBuildTools drv (with ghcPackages; - [ + # This is the version of the Haskell compiler we recommend using. + ghcPackages = haskell.packages.ghc927; + + appendLibraries = drv: lib.addExtraLibraries drv (with pkgs; [ lzma zlib ]); + + appendTools = drv: + lib.addBuildTools drv (with ghcPackages; [ cabal-install haskell-language-server - lzma - ormolu - pkgs.postgresql - zlib + hlint + stylish-haskell ]); + useBroken = drv: pipe drv [ lib.markBroken lib.dontCheck ]; + + modifier = drv: pipe drv [ appendLibraries appendTools ]; + project = ghcPackages.developPackage { root = ./.; - modifier = additionalTools; - overrides = self: super: { openapi3 = pkgs.haskell.lib.dontCheck super.openapi3; }; + modifier = modifier; + overrides = self: super: { openapi3 = useBroken super.openapi3; }; }; -in -project +in project.overrideAttrs (oldAttrs: { + shellHook = '' + function warn() { tput setaf $2; echo "$1"; tput sgr0; } + + tput bold + warn "Welcome to GovTool!" 4 + warn "This is a backend development shell." 4 + warn "Read the ${./README.md} to get more info about this module." 8 + '' + (oldAttrs.shellHook or ""); +}) diff --git a/govtool/backend/misc/migration6.sql b/govtool/backend/misc/migration6.sql index 56e8e0173..53982ee4a 100644 --- a/govtool/backend/misc/migration6.sql +++ b/govtool/backend/misc/migration6.sql @@ -4,4 +4,4 @@ UPDATE governance_action SET temp_column = json_build_object('message', details: ALTER TABLE governance_action DROP COLUMN details; -ALTER TABLE governance_action RENAME COLUMN temp_column TO details; \ No newline at end of file +ALTER TABLE governance_action RENAME COLUMN temp_column TO details; diff --git a/govtool/backend/misc/schema.sql b/govtool/backend/misc/schema.sql index 6221a01e0..6d0d23af6 100644 --- a/govtool/backend/misc/schema.sql +++ b/govtool/backend/misc/schema.sql @@ -396,4 +396,3 @@ GRANT ALL ON SCHEMA public TO PUBLIC; -- -- PostgreSQL database dump complete -- - diff --git a/govtool/backend/misc/schema4.sql b/govtool/backend/misc/schema4.sql index 0a38cee71..1904fb40f 100644 --- a/govtool/backend/misc/schema4.sql +++ b/govtool/backend/misc/schema4.sql @@ -397,4 +397,3 @@ GRANT ALL ON SCHEMA public TO PUBLIC; -- -- PostgreSQL database dump complete -- - diff --git a/govtool/backend/misc/schema5.sql b/govtool/backend/misc/schema5.sql index f100dfd7f..179a9063d 100644 --- a/govtool/backend/misc/schema5.sql +++ b/govtool/backend/misc/schema5.sql @@ -397,4 +397,3 @@ GRANT ALL ON SCHEMA public TO PUBLIC; -- -- PostgreSQL database dump complete -- - diff --git a/govtool/backend/shell.nix b/govtool/backend/shell.nix deleted file mode 100644 index e020a95a2..000000000 --- a/govtool/backend/shell.nix +++ /dev/null @@ -1,16 +0,0 @@ -{ pkgs ? import {} }: -let - project = import ./default.nix { inherit pkgs; }; -in -project.overrideAttrs (attrs: { - buildInputs = attrs.buildInputs ++ (with pkgs; [ - awscli - docker - git - gnumake - ]); - - shellHook = '' - ln -s ${project}/libexec/yarn-nix-example/node_modules node_modules - ''; -}) diff --git a/govtool/backend/sources.nix b/govtool/backend/sources.nix deleted file mode 100644 index c8b57f261..000000000 --- a/govtool/backend/sources.nix +++ /dev/null @@ -1,8 +0,0 @@ -{ - pkgs = import - (builtins.fetchGit { - url = "https://github.com/NixOS/nixpkgs.git"; - rev = "c9ece0059f42e0ab53ac870104ca4049df41b133"; - }) - { }; -} diff --git a/govtool/backend/sql/get-all-proposal-stake-keys.sql b/govtool/backend/sql/get-all-proposal-stake-keys.sql index 136c558b3..458af120a 100644 --- a/govtool/backend/sql/get-all-proposal-stake-keys.sql +++ b/govtool/backend/sql/get-all-proposal-stake-keys.sql @@ -27,4 +27,4 @@ join drep_hash on drep_hash.id = delegation_vote.id join stake_address on stake_address.id = delegation_vote.addr_id -where drep_hash.view = 'AlwaysAbstain' \ No newline at end of file +where drep_hash.view = 'AlwaysAbstain' diff --git a/govtool/backend/sql/get-current-delegation.sql b/govtool/backend/sql/get-current-delegation.sql index d445aa280..68e9dce31 100644 --- a/govtool/backend/sql/get-current-delegation.sql +++ b/govtool/backend/sql/get-current-delegation.sql @@ -10,4 +10,4 @@ join stake_address on stake_address.id = delegation_vote.addr_id where stake_address.hash_raw = decode(?, 'hex') and not exists (select * from delegation_vote as dv2 where dv2.addr_id = delegation_vote.addr_id and dv2.tx_id > delegation_vote.tx_id) -limit 1; \ No newline at end of file +limit 1; diff --git a/govtool/backend/sql/get-current-epoch-params.sql b/govtool/backend/sql/get-current-epoch-params.sql index 80a7eb053..9fd642903 100644 --- a/govtool/backend/sql/get-current-epoch-params.sql +++ b/govtool/backend/sql/get-current-epoch-params.sql @@ -1 +1 @@ -select ROW_TO_JSON(epoch_param) from epoch_param order by epoch_no desc limit 1; \ No newline at end of file +select ROW_TO_JSON(epoch_param) from epoch_param order by epoch_no desc limit 1; diff --git a/govtool/backend/sql/get-delegates.sql b/govtool/backend/sql/get-delegates.sql index bf0a63d5c..f315ef3c4 100644 --- a/govtool/backend/sql/get-delegates.sql +++ b/govtool/backend/sql/get-delegates.sql @@ -2,4 +2,4 @@ SELECT delegation_vote.stake_addr from drep join delegation_vote on delegation_vote.drep_id = drep.id -where drep.drep_raw = ? \ No newline at end of file +where drep.drep_raw = ? diff --git a/govtool/backend/sql/get-network-metrics.sql b/govtool/backend/sql/get-network-metrics.sql index 073312c48..0ab89a170 100644 --- a/govtool/backend/sql/get-network-metrics.sql +++ b/govtool/backend/sql/get-network-metrics.sql @@ -1,44 +1,68 @@ -with current_epoch as ( - select Max(no) as no - from epoch -), current_block as ( - select Max(block_no) as block_no - from block -), unique_delegators as ( - select count(distinct(addr_id)) as count - from delegation_vote -), total_delegations as ( - select count(*) as count - from delegation_vote -), total_gov_action_proposals as ( - select count(distinct(tx_id, index)) as count - from gov_action_proposal -), total_drep_votes as ( - select count(*) as count - from voting_procedure - where voter_role = 'DRep' -), total_registered_dreps as ( - select count(*) as count - from drep_hash -), always_abstain_voting_power as ( - select coalesce(amount, 0) as amount - from drep_hash - left join drep_distr - on drep_hash.id = drep_distr.hash_id - where drep_hash.view = 'drep_always_abstain' - order by epoch_no desc - limit 1 -), always_no_confidence_voting_power as ( - select coalesce(amount, 0) as amount - from drep_hash - left join drep_distr - on drep_hash.id = drep_distr.hash_id - where drep_hash.view = 'drep_always_no_confidence' - order by epoch_no desc - limit 1 +WITH current_epoch AS ( + SELECT + Max(NO) AS no + FROM + epoch +), +current_block AS ( + SELECT + Max(block_no) AS block_no + FROM + block +), +unique_delegators AS ( + SELECT + count(DISTINCT (addr_id)) AS count + FROM + delegation_vote +), +total_delegations AS ( + SELECT + count(*) AS count + FROM + delegation_vote +), +total_gov_action_proposals AS ( + SELECT + count(DISTINCT (tx_id, INDEX)) AS count + FROM + gov_action_proposal +), +total_drep_votes AS ( + SELECT + count(*) AS count + FROM + voting_procedure + WHERE + voter_role = 'DRep' +), +total_registered_dreps AS ( + SELECT + count(*) AS count + FROM + drep_hash +), +always_abstain_voting_power AS ( + SELECT + coalesce(( + SELECT + amount + FROM drep_hash + LEFT JOIN drep_distr ON drep_hash.id = drep_distr.hash_id + WHERE + drep_hash.view = 'drep_always_abstain' ORDER BY epoch_no DESC LIMIT 1), 0) AS amount +), +always_no_confidence_voting_power AS ( + SELECT + coalesce(( + SELECT + amount + FROM drep_hash + LEFT JOIN drep_distr ON drep_hash.id = drep_distr.hash_id + WHERE + drep_hash.view = 'drep_always_no_confidence' ORDER BY epoch_no DESC LIMIT 1), 0) AS amount ) - -select +SELECT current_epoch.no, current_block.block_no, unique_delegators.count, @@ -48,12 +72,13 @@ select total_registered_dreps.count, always_abstain_voting_power.amount, always_no_confidence_voting_power.amount -from current_epoch -cross join current_block -cross join unique_delegators -cross join total_delegations -cross join total_gov_action_proposals -cross join total_drep_votes -cross join total_registered_dreps -cross join always_abstain_voting_power -cross join always_no_confidence_voting_power +FROM + current_epoch + CROSS JOIN current_block + CROSS JOIN unique_delegators + CROSS JOIN total_delegations + CROSS JOIN total_gov_action_proposals + CROSS JOIN total_drep_votes + CROSS JOIN total_registered_dreps + CROSS JOIN always_abstain_voting_power + CROSS JOIN always_no_confidence_voting_power diff --git a/govtool/backend/sql/get-stake-key-voting-power.sql b/govtool/backend/sql/get-stake-key-voting-power.sql index 7a128045b..59aa5049b 100644 --- a/govtool/backend/sql/get-stake-key-voting-power.sql +++ b/govtool/backend/sql/get-stake-key-voting-power.sql @@ -3,4 +3,4 @@ from stake_address join utxo_view on utxo_view.stake_address_id = stake_address.id where stake_address.hash_raw = decode(?, 'hex') -group by stake_address.hash_raw \ No newline at end of file +group by stake_address.hash_raw diff --git a/govtool/backend/sql/get-transaction-status.sql b/govtool/backend/sql/get-transaction-status.sql index d368945ec..ff21785e7 100644 --- a/govtool/backend/sql/get-transaction-status.sql +++ b/govtool/backend/sql/get-transaction-status.sql @@ -1 +1 @@ -select exists (select * from tx where tx.hash = decode(?, 'hex')) \ No newline at end of file +select exists (select * from tx where tx.hash = decode(?, 'hex')) diff --git a/govtool/backend/sql/get-votes.sql b/govtool/backend/sql/get-votes.sql index 7fa5dc935..18ff422fd 100644 --- a/govtool/backend/sql/get-votes.sql +++ b/govtool/backend/sql/get-votes.sql @@ -1,4 +1,4 @@ -select DISTINCT ON (voting_procedure.gov_action_proposal_id, voting_procedure.drep_voter) voting_procedure.gov_action_proposal_id, concat(encode(tx.hash,'hex'),'#',gov_action_proposal.index), encode(drep_hash.raw, 'hex'), voting_procedure.vote::text, voting_anchor.url, encode(voting_anchor.data_hash, 'hex') +select DISTINCT ON (voting_procedure.gov_action_proposal_id, voting_procedure.drep_voter) voting_procedure.gov_action_proposal_id, concat(encode(tx.hash,'hex'),'#',gov_action_proposal.index), encode(drep_hash.raw, 'hex'), voting_procedure.vote::text, voting_anchor.url, encode(voting_anchor.data_hash, 'hex'), block.epoch_no as epoch_no, block.time as time from voting_procedure join gov_action_proposal on gov_action_proposal.id = voting_procedure.gov_action_proposal_id @@ -8,7 +8,7 @@ left join voting_anchor on voting_anchor.id = voting_procedure.voting_anchor_id join tx on tx.id = gov_action_proposal.tx_id +join block +on block.id = tx.block_id where drep_hash.raw = decode(?, 'hex') order by voting_procedure.gov_action_proposal_id, voting_procedure.drep_voter, voting_procedure.id desc - - diff --git a/govtool/backend/sql/get-voting-power.sql b/govtool/backend/sql/get-voting-power.sql index b81b5eb84..3a7cbbfb7 100644 --- a/govtool/backend/sql/get-voting-power.sql +++ b/govtool/backend/sql/get-voting-power.sql @@ -4,4 +4,4 @@ left join drep_distr on drep_hash.id = drep_distr.hash_id where drep_hash.raw = decode(?,'hex') order by epoch_no desc -limit 1 \ No newline at end of file +limit 1 diff --git a/govtool/backend/sql/list-proposals.sql b/govtool/backend/sql/list-proposals.sql index b7bb42a6b..f0bc3cc13 100644 --- a/govtool/backend/sql/list-proposals.sql +++ b/govtool/backend/sql/list-proposals.sql @@ -1,86 +1,123 @@ WITH LatestDrepDistr AS ( SELECT *, - ROW_NUMBER() OVER(PARTITION BY hash_id ORDER BY epoch_no DESC) AS rn - FROM drep_distr -), EpochUtils AS ( + ROW_NUMBER() OVER (PARTITION BY hash_id ORDER BY epoch_no DESC) AS rn + FROM + drep_distr +), +EpochUtils AS ( SELECT - (Max(end_time)-Min(end_time)) / (Max(no)-Min(no)) as epoch_duration, - Max(no) as last_epoch_no, - Max(end_time) as last_epoch_end_time - FROM epoch -), always_no_confidence_voting_power as ( - select coalesce(amount, 0) as amount - from drep_hash - left join drep_distr - on drep_hash.id = drep_distr.hash_id - where drep_hash.view = 'drep_always_no_confidence' - order by epoch_no desc - limit 1 -), always_abstain_voting_power as ( - select coalesce(amount, 0) as amount - from drep_hash - left join drep_distr - on drep_hash.id = drep_distr.hash_id - where drep_hash.view = 'drep_always_abstain' - order by epoch_no desc - limit 1 + (Max(end_time) - Min(end_time)) /(Max(NO) - Min(NO)) AS epoch_duration, + Max(NO) AS last_epoch_no, + Max(end_time) AS last_epoch_end_time + FROM + epoch +), +always_no_confidence_voting_power AS ( + SELECT + coalesce(( + SELECT + amount + FROM drep_hash + LEFT JOIN drep_distr ON drep_hash.id = drep_distr.hash_id + WHERE + drep_hash.view = 'drep_always_no_confidence' ORDER BY epoch_no DESC LIMIT 1), 0) AS amount +), +always_abstain_voting_power AS ( + SELECT + coalesce(( + SELECT + amount + FROM drep_hash + LEFT JOIN drep_distr ON drep_hash.id = drep_distr.hash_id + WHERE + drep_hash.view = 'drep_always_abstain' ORDER BY epoch_no DESC LIMIT 1), 0) AS amount ) - -select +SELECT gov_action_proposal.id, encode(creator_tx.hash, 'hex'), gov_action_proposal.index, gov_action_proposal.type::text, - gov_action_proposal.description::json, - epoch_utils.last_epoch_end_time + epoch_utils.epoch_duration*(gov_action_proposal.expiration - epoch_utils.last_epoch_no), - creator_block.time, /* created date */ - voting_anchor.url, - encode(voting_anchor.data_hash, 'hex'), - - coalesce(Sum(ldd.amount) filter (where voting_procedure.vote::text = 'Yes'),0) - + (case - when gov_action_proposal.type = 'NoConfidence' then always_no_confidence_voting_power.amount - else 0 - end) "yes_votes", - coalesce(Sum(ldd.amount) filter (where voting_procedure.vote::text = 'No'),0) + - (case - when gov_action_proposal.type = 'NoConfidence' then 0 - else always_no_confidence_voting_power.amount - end) "no_votes", - coalesce(Sum(ldd.amount) filter (where voting_procedure.vote::text = 'Abstain'),0) + always_abstain_voting_power.amount "abstain_votes" -from gov_action_proposal -cross join EpochUtils as epoch_utils -cross join always_no_confidence_voting_power -cross join always_abstain_voting_power -join tx as creator_tx -on creator_tx.id = gov_action_proposal.tx_id -join block as creator_block -on creator_block.id = creator_tx.block_id -join voting_anchor -on voting_anchor.id = gov_action_proposal.voting_anchor_id -left join voting_procedure -on voting_procedure.gov_action_proposal_id = gov_action_proposal.id -left join LatestDrepDistr ldd -on ldd.hash_id = voting_procedure.drep_voter and ldd.rn = 1 + ( + case when gov_action_proposal.type = 'TreasuryWithdrawals' then + json_build_object('Reward Address', stake_address.view, 'Amount', treasury_withdrawal.amount) -where (not ? or (concat(encode(creator_tx.hash,'hex'),'#',gov_action_proposal.index) in ?)) -and gov_action_proposal.expiration > (select Max(no) from epoch) -and gov_action_proposal.ratified_epoch is null -and gov_action_proposal.enacted_epoch is null -and gov_action_proposal.expired_epoch is null -and gov_action_proposal.dropped_epoch is null - -group by ( - gov_action_proposal.id, - gov_action_proposal.index, - creator_tx.hash, + when gov_action_proposal.type::text = 'InfoAction' then + json_build_object() + else + null + end + ) as description, + epoch_utils.last_epoch_end_time + epoch_utils.epoch_duration *(gov_action_proposal.expiration - epoch_utils.last_epoch_no), + gov_action_proposal.expiration, creator_block.time, - epoch_utils.epoch_duration, - epoch_utils.last_epoch_no, - epoch_utils.last_epoch_end_time, + creator_block.epoch_no, + /* created date */ voting_anchor.url, - voting_anchor.data_hash, - always_no_confidence_voting_power.amount, - always_abstain_voting_power.amount - ) + encode(voting_anchor.data_hash, 'hex'), + off_chain_vote_data.title, + off_chain_vote_data.abstract, + off_chain_vote_data.motivation, + off_chain_vote_data.rationale, + off_chain_vote_data.json, + coalesce(Sum(ldd.amount) FILTER (WHERE voting_procedure.vote::text = 'Yes'), 0) +( + CASE WHEN gov_action_proposal.type = 'NoConfidence' THEN + always_no_confidence_voting_power.amount + ELSE + 0 + END) "yes_votes", + coalesce(Sum(ldd.amount) FILTER (WHERE voting_procedure.vote::text = 'No'), 0) +( + CASE WHEN gov_action_proposal.type = 'NoConfidence' THEN + 0 + ELSE + always_no_confidence_voting_power.amount + END) "no_votes", + coalesce(Sum(ldd.amount) FILTER (WHERE voting_procedure.vote::text = 'Abstain'), 0) + always_abstain_voting_power.amount "abstain_votes" +FROM + gov_action_proposal + LEFT JOIN treasury_withdrawal + on gov_action_proposal.id = treasury_withdrawal.gov_action_proposal_id + LEFT JOIN stake_address + on stake_address.id = treasury_withdrawal.stake_address_id + + CROSS JOIN EpochUtils AS epoch_utils + CROSS JOIN always_no_confidence_voting_power + CROSS JOIN always_abstain_voting_power + JOIN tx AS creator_tx ON creator_tx.id = gov_action_proposal.tx_id + JOIN block AS creator_block ON creator_block.id = creator_tx.block_id + LEFT JOIN voting_anchor ON voting_anchor.id = gov_action_proposal.voting_anchor_id + LEFT JOIN off_chain_vote_data ON off_chain_vote_data.voting_anchor_id = voting_anchor.id + LEFT JOIN voting_procedure ON voting_procedure.gov_action_proposal_id = gov_action_proposal.id + LEFT JOIN LatestDrepDistr ldd ON ldd.hash_id = voting_procedure.drep_voter + AND ldd.rn = 1 +WHERE (NOT ? + OR (concat(encode(creator_tx.hash, 'hex'), '#', gov_action_proposal.index) IN ?)) +AND gov_action_proposal.expiration >( + SELECT + Max(NO) + FROM + epoch) +AND gov_action_proposal.ratified_epoch IS NULL +AND gov_action_proposal.enacted_epoch IS NULL +AND gov_action_proposal.expired_epoch IS NULL +AND gov_action_proposal.dropped_epoch IS NULL +GROUP BY + (gov_action_proposal.id, + stake_address.view, + treasury_withdrawal.amount, + creator_block.epoch_no, + off_chain_vote_data.title, + off_chain_vote_data.abstract, + off_chain_vote_data.motivation, + off_chain_vote_data.rationale, + off_chain_vote_data.json, + gov_action_proposal.index, + creator_tx.hash, + creator_block.time, + epoch_utils.epoch_duration, + epoch_utils.last_epoch_no, + epoch_utils.last_epoch_end_time, + voting_anchor.url, + voting_anchor.data_hash, + always_no_confidence_voting_power.amount, + always_abstain_voting_power.amount) diff --git a/govtool/backend/src/VVA/API.hs b/govtool/backend/src/VVA/API.hs index c303fe32f..293e0c2bd 100644 --- a/govtool/backend/src/VVA/API.hs +++ b/govtool/backend/src/VVA/API.hs @@ -1,38 +1,46 @@ -{-# LANGUAGE DataKinds #-} -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE NamedFieldPuns #-} -{-# LANGUAGE RecordWildCards #-} -{-# LANGUAGE TypeOperators #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE ViewPatterns #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE TypeOperators #-} +{-# LANGUAGE ViewPatterns #-} module VVA.API where -import Control.Monad.Reader -import Control.Monad.Except (throwError) -import Data.List (sortOn) -import Data.Maybe (fromMaybe, Maybe (Nothing)) -import Data.Ord (Down (..)) -import Data.Text hiding (elem, filter, map, null, take, drop, length) -import Servant.API -import Servant.Server -import Text.Read (readMaybe) -import VVA.API.Types -import qualified VVA.AdaHolder as AdaHolder -import VVA.Config -import qualified VVA.DRep as DRep -import qualified VVA.Proposal as Proposal -import qualified VVA.Epoch as Epoch -import qualified VVA.Transaction as Transaction -import Data.Bool (Bool) -import qualified Data.Map as Map -import VVA.Cache (cacheRequest) -import Control.Exception (throw) -import VVA.Types (CacheEnv(..), AppError(ValidationError, CriticalError), App, AppEnv(..)) -import qualified VVA.Types as Types -import qualified Data.Text as Text -import VVA.Network as Network -import Numeric.Natural (Natural) +import Control.Exception (throw) +import Control.Monad.Except (throwError) +import Control.Monad.Reader + +import Data.Bool (Bool) +import Data.List (sortOn) +import qualified Data.Map as Map +import Data.Maybe (Maybe (Nothing), fromMaybe) +import Data.Ord (Down (..)) +import Data.Text hiding (drop, elem, filter, length, map, + null, take) +import qualified Data.Text as Text + +import Numeric.Natural (Natural) + +import Servant.API +import Servant.Server + +import Text.Read (readMaybe) + +import qualified VVA.AdaHolder as AdaHolder +import VVA.API.Types +import VVA.Cache (cacheRequest) +import VVA.Config +import qualified VVA.DRep as DRep +import qualified VVA.Epoch as Epoch +import VVA.Network as Network +import qualified VVA.Proposal as Proposal +import qualified VVA.Transaction as Transaction +import qualified VVA.Types as Types +import VVA.Types (App, AppEnv (..), + AppError (CriticalError, ValidationError), + CacheEnv (..)) type VVAApi = "drep" :> "list" :> QueryParam "drepView" Text :> Get '[JSON] [DRep] @@ -47,6 +55,7 @@ type VVAApi = :> QueryParam "page" Natural :> QueryParam "pageSize" Natural :> QueryParam "drepId" HexText + :> QueryParam "search" Text :> Get '[JSON] ListProposalsResponse :<|> "proposal" :> "get" :> Capture "proposalId" GovActionId :> QueryParam "drepId" HexText :> Get '[JSON] GetProposalResponse :<|> "epoch" :> "params" :> Get '[JSON] GetCurrentEpochParamsResponse @@ -70,12 +79,12 @@ server = drepList mapDRepType :: Types.DRepType -> DRepType -mapDRepType Types.DRep = NormalDRep +mapDRepType Types.DRep = NormalDRep mapDRepType Types.SoleVoter = SoleVoter mapDRepStatus :: Types.DRepStatus -> DRepStatus -mapDRepStatus Types.Retired = Retired -mapDRepStatus Types.Active = Active +mapDRepStatus Types.Retired = Retired +mapDRepStatus Types.Active = Active mapDRepStatus Types.Inactive = Inactive drepRegistrationToDrep :: Types.DRepRegistration -> DRep @@ -98,9 +107,9 @@ drepList mDRepView = do let filtered = flip filter dreps $ \Types.DRepRegistration {..} -> case (dRepRegistrationType, mDRepView) of (Types.SoleVoter, Just x) -> x == dRepRegistrationView - (Types.DRep, Just x) -> isInfixOf x dRepRegistrationView - (Types.DRep, Nothing) -> True - _ -> False + (Types.DRep, Just x) -> x `isInfixOf` dRepRegistrationView + (Types.DRep, Nothing) -> True + _ -> False return $ map drepRegistrationToDrep filtered getVotingPower :: App m => HexText -> m Integer @@ -116,11 +125,18 @@ proposalToResponse Types.Proposal {..} = proposalResponseTxHash = HexText proposalTxHash, proposalResponseIndex = proposalIndex, proposalResponseType = fromMaybe InfoAction $ readMaybe $ unpack proposalType, - proposalResponseDetails = GovernanceActionDetails proposalDetails, + proposalResponseDetails = GovernanceActionDetails <$> proposalDetails, proposalResponseExpiryDate = proposalExpiryDate, + proposalResponseExpiryEpochNo = proposalExpiryEpochNo, proposalResponseCreatedDate = proposalCreatedDate, + proposalResponseCreatedEpochNo = proposalCreatedEpochNo, proposalResponseUrl = proposalUrl, proposalResponseMetadataHash = HexText proposalDocHash, + proposalResponseTitle = proposalTitle, + proposalResponseAbout = proposalAbout, + proposalResponseMotivation = proposalMotivaiton, + proposalResponseRationale = proposalRationale, + proposalResponseMetadata = GovernanceActionMetadata <$> proposalMetadata, proposalResponseYesVotes = proposalYesVotes, proposalResponseNoVotes = proposalNoVotes, proposalResponseAbstainVotes = proposalAbstainVotes @@ -133,7 +149,9 @@ voteToResponse Types.Vote {..} = voteParamsDrepId = HexText voteDrepId, voteParamsVote = voteVote, voteParamsUrl = voteUrl, - voteParamsMetadataHash = HexText <$> voteDocHash + voteParamsMetadataHash = HexText <$> voteDocHash, + voteParamsEpochNo = voteEpochNo, + voteParamsDate = voteDate } @@ -157,10 +175,10 @@ mapSortAndFilterProposals selectedTypes sortMode proposals = ) mappedProposals sortedProposals = case sortMode of - Nothing -> filteredProposals - Just NewestCreated -> sortOn (Down . proposalResponseCreatedDate) filteredProposals + Nothing -> filteredProposals + Just NewestCreated -> sortOn (Down . proposalResponseCreatedDate) filteredProposals Just SoonestToExpire -> sortOn proposalResponseExpiryDate filteredProposals - Just MostYesVotes -> sortOn (Down . proposalResponseYesVotes) filteredProposals + Just MostYesVotes -> sortOn (Down . proposalResponseYesVotes) filteredProposals in sortedProposals getVotes :: App m => HexText -> [GovernanceActionType] -> Maybe GovernanceActionSortMode -> m [VoteResponse] @@ -171,7 +189,7 @@ getVotes (unHexText -> dRepId) selectedTypes sortMode = do let processedProposals = mapSortAndFilterProposals selectedTypes sortMode proposals return $ [ VoteResponse - { voteResponseVote = voteToResponse (voteMap Map.! (read $ unpack proposalResponseId)) + { voteResponseVote = voteToResponse (voteMap Map.! read (unpack proposalResponseId)) , voteResponseProposal = proposalResponse } | proposalResponse@ProposalResponse{proposalResponseId} <- processedProposals @@ -201,7 +219,7 @@ getCurrentDelegation (unHexText -> stakeKey) = do getStakeKeyVotingPower :: App m => HexText -> m Integer getStakeKeyVotingPower (unHexText -> stakeKey) = do CacheEnv {adaHolderVotingPowerCache} <- asks vvaCache - cacheRequest adaHolderVotingPowerCache stakeKey $ AdaHolder.getStakeKeyVotingPower $ stakeKey + cacheRequest adaHolderVotingPowerCache stakeKey $ AdaHolder.getStakeKeyVotingPower stakeKey listProposals @@ -211,8 +229,9 @@ listProposals -> Maybe Natural -> Maybe Natural -> Maybe HexText + -> Maybe Text -> m ListProposalsResponse -listProposals selectedTypes sortMode mPage mPageSize mDrepRaw = do +listProposals selectedTypes sortMode mPage mPageSize mDrepRaw mSearchQuery = do let page = (fromIntegral $ fromMaybe 0 mPage) :: Int pageSize = (fromIntegral $ fromMaybe 10 mPageSize) :: Int @@ -223,15 +242,31 @@ listProposals selectedTypes sortMode mPage mPageSize mDrepRaw = do map (voteParamsProposalId . voteResponseVote) <$> getVotes drepId [] Nothing + + + let filterF ProposalResponse{..} = case Text.toLower <$> mSearchQuery of + Nothing -> True + Just searchQuery -> fromMaybe False $ do + title <- Text.toLower <$> proposalResponseTitle + about <- Text.toLower <$> proposalResponseAbout + motivation <- Text.toLower <$> proposalResponseMotivation + rationale <- Text.toLower <$> proposalResponseRationale + let govActionId = unHexText proposalResponseTxHash <> "#" <> Text.pack (show proposalResponseIndex) + let result = searchQuery `isInfixOf` title + || searchQuery `isInfixOf` about + || searchQuery `isInfixOf` motivation + || searchQuery `isInfixOf` rationale + || searchQuery `isInfixOf` govActionId + + pure result + CacheEnv {proposalListCache} <- asks vvaCache mappedAndSortedProposals <- filter - ( \ProposalResponse {proposalResponseId} -> + ( \p@ProposalResponse {proposalResponseId} -> proposalResponseId `notElem` proposalsToRemove - ) - <$> - mapSortAndFilterProposals selectedTypes sortMode - <$> cacheRequest proposalListCache () Proposal.listProposals + && filterF p + ) . mapSortAndFilterProposals selectedTypes sortMode <$> cacheRequest proposalListCache () Proposal.listProposals let total = length mappedAndSortedProposals :: Int @@ -244,7 +279,7 @@ listProposals selectedTypes sortMode mPage mPageSize mDrepRaw = do , listProposalsResponseElements = elements } -getProposal :: App m => GovActionId -> Maybe (HexText) -> m GetProposalResponse +getProposal :: App m => GovActionId -> Maybe HexText -> m GetProposalResponse getProposal g@(GovActionId govActionTxHash govActionIndex) mDrepId' = do let mDrepId = unHexText <$> mDrepId' CacheEnv {getProposalCache} <- asks vvaCache @@ -273,7 +308,7 @@ getTransactionStatus :: App m => HexText -> m GetTransactionStatusResponse getTransactionStatus (unHexText -> transactionId) = do x <- Transaction.getTransactionStatus transactionId case x of - Types.TransactionConfirmed -> return $ GetTransactionStatusResponse True + Types.TransactionConfirmed -> return $ GetTransactionStatusResponse True Types.TransactionUnconfirmed -> return $ GetTransactionStatusResponse False throw500 :: App m => m () @@ -294,4 +329,4 @@ getNetworkMetrics = do , getNetworkMetricsResponseTotalRegisteredDReps = networkMetricsTotalRegisteredDReps , getNetworkMetricsResponseAlwaysAbstainVotingPower = networkMetricsAlwaysAbstainVotingPower , getNetworkMetricsResponseAlwaysNoConfidenceVotingPower = networkMetricsAlwaysNoConfidenceVotingPower - } \ No newline at end of file + } diff --git a/govtool/backend/src/VVA/API/Types.hs b/govtool/backend/src/VVA/API/Types.hs index efc79857d..a591305b6 100644 --- a/govtool/backend/src/VVA/API/Types.hs +++ b/govtool/backend/src/VVA/API/Types.hs @@ -1,60 +1,68 @@ -{-# LANGUAGE ConstraintKinds #-} -{-# LANGUAGE DeriveAnyClass #-} -{-# LANGUAGE DeriveGeneric #-} -{-# LANGUAGE DerivingStrategies #-} -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE TemplateHaskell #-} -{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE ConstraintKinds #-} +{-# LANGUAGE DeriveAnyClass #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} -{-# LANGUAGE NamedFieldPuns #-} -{-# LANGUAGE MultiParamTypeClasses #-} -{-# LANGUAGE FlexibleInstances #-} -{-# LANGUAGE RecordWildCards #-} -{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE NamedFieldPuns #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE TypeApplications #-} module VVA.API.Types where -import Control.Lens ((?~), (.~)) -import Control.Monad.Except -import Control.Monad.Reader -import Data.Aeson -import qualified Data.Aeson as Aeson -import qualified Data.Text.Lazy.Encoding as Text +import Control.Exception (throw) +import Control.Lens ((.~), (?~)) +import Control.Monad (guard) +import Control.Monad.Except +import Control.Monad.Reader + +import Data.Aeson +import qualified Data.Aeson as Aeson +import qualified Data.Aeson.KeyMap as Aeson (toList) +import Data.Aeson.TH (deriveJSON) import qualified Data.ByteString.Lazy.Char8 as Char8 -import qualified Data.Aeson.KeyMap as Aeson (toList) -import Data.Aeson.TH (deriveJSON) -import Data.Function ((&)) -import Data.Maybe (fromJust, fromMaybe) -import Data.OpenApi hiding (Info) -import Data.Text hiding (map) -import qualified Data.Text as Text -import Data.Time -import GHC.Generics -import Servant.API (FromHttpApiData, parseUrlPiece, parseQueryParam) -import Text.Read (readMaybe) -import VVA.API.Utils -import VVA.Config -import GHC.Exts (toList) -import qualified Data.Cache as Cache -import qualified VVA.Proposal as Proposal -import Data.Hashable (Hashable) -import Data.Has (Has, getter, modifier) -import Data.Pool (Pool) -import Database.PostgreSQL.Simple (Connection) -import Data.Char (isHexDigit) -import VVA.Types (AppError(ValidationError)) -import Data.Proxy (Proxy(Proxy)) -import Control.Exception (throw) -import Data.Swagger.Internal (SwaggerType(SwaggerString)) -import Control.Monad (guard) - -newtype HexText = HexText { unHexText :: Text } - deriving newtype (Show, Eq) +import qualified Data.Cache as Cache +import Data.Char (isHexDigit) +import Data.Function ((&)) +import Data.Has (Has, getter, modifier) +import Data.Hashable (Hashable) +import Data.Maybe (fromJust, fromMaybe) +import Data.OpenApi hiding (Info) +import Data.Pool (Pool) +import Data.Proxy (Proxy (Proxy)) +import Data.Swagger.Internal (SwaggerType (SwaggerString)) +import Data.Text hiding (map) +import qualified Data.Text as Text +import qualified Data.Text.Lazy.Encoding as Text +import Data.Time + +import Database.PostgreSQL.Simple (Connection) + +import GHC.Exts (toList) +import GHC.Generics + +import Servant.API (FromHttpApiData, parseQueryParam, + parseUrlPiece) + +import Text.Read (readMaybe) + +import VVA.API.Utils +import VVA.Config +import qualified VVA.Proposal as Proposal +import VVA.Types (AppError (ValidationError)) + +newtype HexText + = HexText { unHexText :: Text } + deriving newtype (Eq, Show) instance FromJSON HexText where parseJSON (Aeson.String t) = do - if (Text.length t `mod` 2 == 1 || Text.any (not . isHexDigit) t) + if Text.length t `mod` 2 == 1 || Text.any (not . isHexDigit) t then mzero else pure $ HexText t @@ -64,7 +72,7 @@ instance ToJSON HexText where -- To use it in routes, we need to be able to parse it from Text: instance FromHttpApiData HexText where parseUrlPiece txt - | Text.all isHexDigit txt && (Text.length txt `mod` 2 == 0) = Right (HexText txt) + | Text.all isHexDigit txt && even (Text.length txt) = Right (HexText txt) | otherwise = Left "Not a valid hex value" @@ -82,10 +90,11 @@ instance ToSchema HexText where & schema . format ?~ "hex" & schema . example ?~ toJSON (HexText "a1b2c3") -data GovActionId = GovActionId - { govActionIdTxHash :: HexText - , govActionIdIndex :: Integer - } +data GovActionId + = GovActionId + { govActionIdTxHash :: HexText + , govActionIdIndex :: Integer + } deriving (Eq) instance Show GovActionId where @@ -135,15 +144,14 @@ instance ToSchema GovActionId where & schema . example ?~ Aeson.String exampleGovActionId -data GovernanceActionType - = ParameterChange - | HardForkInitiation - | TreasuryWithdrawals - | NoConfidence - | NewCommittee - | NewConstitution - | InfoAction - deriving (Eq, Show, Read, Enum, Bounded, Generic) +data GovernanceActionType = ParameterChange | HardForkInitiation | TreasuryWithdrawals | NoConfidence | NewCommittee | NewConstitution | InfoAction deriving + ( Bounded + , Enum + , Eq + , Generic + , Read + , Show + ) instance FromJSON GovernanceActionType where parseJSON (Aeson.String governanceActionType) = pure $ fromMaybe InfoAction $ readMaybe (Text.unpack governanceActionType) @@ -163,7 +171,7 @@ instance ToSchema GovernanceActionType where instance FromHttpApiData GovernanceActionType where parseQueryParam t = case readMaybe $ Text.unpack t of - Just x -> Right x + Just x -> Right x Nothing -> Left ("incorrect governance action type: " <> t) instance ToParamSchema GovernanceActionType where @@ -172,11 +180,14 @@ instance ToParamSchema GovernanceActionType where & type_ ?~ OpenApiString & enum_ ?~ map toJSON (enumFromTo minBound maxBound :: [GovernanceActionType]) -data GovernanceActionSortMode - = SoonestToExpire - | NewestCreated - | MostYesVotes - deriving (Eq, Show, Read, Enum, Bounded, Generic) +data GovernanceActionSortMode = SoonestToExpire | NewestCreated | MostYesVotes deriving + ( Bounded + , Enum + , Eq + , Generic + , Read + , Show + ) instance FromJSON GovernanceActionSortMode where parseJSON (Aeson.String governanceActionSortMode) = pure $ fromJust $ readMaybe (Text.unpack governanceActionSortMode) @@ -196,7 +207,7 @@ instance ToSchema GovernanceActionSortMode where instance FromHttpApiData GovernanceActionSortMode where parseQueryParam t = case readMaybe $ Text.unpack t of - Just x -> Right x + Just x -> Right x Nothing -> Left ("incorrect governance action sort mode: " <> t) instance ToParamSchema GovernanceActionSortMode where @@ -206,7 +217,8 @@ instance ToParamSchema GovernanceActionSortMode where & enum_ ?~ map toJSON (enumFromTo minBound maxBound :: [GovernanceActionSortMode]) -newtype GovernanceActionDetails = GovernanceActionDetails { getValue :: Value } +newtype GovernanceActionDetails + = GovernanceActionDetails { getValue :: Value } deriving newtype (Show) instance FromJSON GovernanceActionDetails where @@ -216,8 +228,8 @@ instance FromJSON GovernanceActionDetails where (Aeson.Object _) -> fail "GovernanceActionDetails cannot have nested objects" (Aeson.Array a) -> forM_ (toList a) $ \case (Aeson.Object _) -> fail "GovernanceActionDetails cannot have nested objects" - (Aeson.Array _) -> fail "GovernanceActionDetails cannot have nested arrays" - _ -> pure () + (Aeson.Array _) -> fail "GovernanceActionDetails cannot have nested arrays" + _ -> pure () _ -> pure () return $ GovernanceActionDetails v parseJSON _ = fail "GovernanceActionDetails has to be an object" @@ -233,20 +245,50 @@ instance ToSchema GovernanceActionDetails where ?~ toJSON ("{\"some_key\": \"some value\", \"some_key2\": [1,2,3]}" :: Text) -data ProposalResponse = ProposalResponse - { proposalResponseId :: Text, - proposalResponseTxHash :: HexText, - proposalResponseIndex :: Integer, - proposalResponseType :: GovernanceActionType, - proposalResponseDetails :: GovernanceActionDetails, - proposalResponseExpiryDate :: Maybe UTCTime, - proposalResponseCreatedDate :: UTCTime, - proposalResponseUrl :: Text, - proposalResponseMetadataHash :: HexText, - proposalResponseYesVotes :: Integer, - proposalResponseNoVotes :: Integer, - proposalResponseAbstainVotes :: Integer - } + +newtype GovernanceActionMetadata + = GovernanceActionMetadata Value + deriving newtype (Show) + +instance FromJSON GovernanceActionMetadata where + parseJSON v@(Aeson.Object o) = pure (GovernanceActionMetadata v) + parseJSON _ = fail "GovernanceActionMetadata has to be an object" + +instance ToJSON GovernanceActionMetadata where + toJSON (GovernanceActionMetadata g) = g + +instance ToSchema GovernanceActionMetadata where + declareNamedSchema _ = pure $ NamedSchema (Just "GovernanceActionMetadata") $ mempty + & type_ ?~ OpenApiObject + & description ?~ "A Governance Action metadata" + & example + ?~ toJSON + ("{\"some_key\": \"some value\", \"some_key2\": [1,2,3]}" :: Text) + + + +data ProposalResponse + = ProposalResponse + { proposalResponseId :: Text + , proposalResponseTxHash :: HexText + , proposalResponseIndex :: Integer + , proposalResponseType :: GovernanceActionType + , proposalResponseDetails :: Maybe GovernanceActionDetails + , proposalResponseExpiryDate :: Maybe UTCTime + , proposalResponseExpiryEpochNo :: Maybe Integer + , proposalResponseCreatedDate :: UTCTime + , proposalResponseCreatedEpochNo :: Integer + , proposalResponseUrl :: Text + , proposalResponseMetadataHash :: HexText + , proposalResponseTitle :: Maybe Text + , proposalResponseAbout :: Maybe Text + , proposalResponseMotivation :: Maybe Text + , proposalResponseRationale :: Maybe Text + , proposalResponseMetadata :: Maybe GovernanceActionMetadata + , proposalResponseYesVotes :: Integer + , proposalResponseNoVotes :: Integer + , proposalResponseAbstainVotes :: Integer + } deriving (Generic, Show) deriveJSON (jsonOptions "proposalResponse") ''ProposalResponse @@ -258,9 +300,16 @@ exampleProposalResponse = "{ \"id\": \"proposalId123\"," <> "\"type\": \"InfoAction\"," <> "\"details\": \"some details\"," <> "\"expiryDate\": \"1970-01-01T00:00:00Z\"," + <> "\"expiryEpochNo\": 0," <> "\"createdDate\": \"1970-01-01T00:00:00Z\"," + <> "\"createdEpochNo\": 0," <> "\"url\": \"https://proposal.metadata.xyz\"," <> "\"metadataHash\": \"9af10e89979e51b8cdc827c963124a1ef4920d1253eef34a1d5cfe76438e3f11\"," + <> "\"title\": \"Proposal Title\"," + <> "\"about\": \"Proposal About\"," + <> "\"motivation\": \"Proposal Motivation\"," + <> "\"rationale\": \"Proposal Rationale\"," + <> "\"metadata\": {\"key\": \"value\"}," <> "\"yesVotes\": 0," <> "\"noVotes\": 0," <> "\"abstainVotes\": 0}" @@ -288,12 +337,14 @@ exampleListProposalsResponse = <> "\"elements\": [" <> exampleProposalResponse <> "]}" -data ListProposalsResponse = ListProposalsResponse - { listProposalsResponsePage :: Integer - , listProposalsResponsePageSize :: Integer - , listProposalsResponseTotal :: Integer - , listProposalsResponseElements :: [ProposalResponse] - } deriving (Generic, Show) +data ListProposalsResponse + = ListProposalsResponse + { listProposalsResponsePage :: Integer + , listProposalsResponsePageSize :: Integer + , listProposalsResponseTotal :: Integer + , listProposalsResponseElements :: [ProposalResponse] + } + deriving (Generic, Show) deriveJSON (jsonOptions "listProposalsResponse") ''ListProposalsResponse @@ -312,13 +363,16 @@ instance ToSchema ListProposalsResponse where & example ?~ toJSON exampleListProposalsResponse -data VoteParams = VoteParams - { voteParamsProposalId :: Text, - voteParamsDrepId :: HexText, - voteParamsVote :: Text, - voteParamsUrl :: Maybe Text, - voteParamsMetadataHash :: Maybe HexText - } +data VoteParams + = VoteParams + { voteParamsProposalId :: Text + , voteParamsDrepId :: HexText + , voteParamsVote :: Text + , voteParamsUrl :: Maybe Text + , voteParamsMetadataHash :: Maybe HexText + , voteParamsEpochNo :: Integer + , voteParamsDate :: UTCTime + } deriving (Generic, Show) deriveJSON (jsonOptions "voteParams") ''VoteParams @@ -329,7 +383,9 @@ exampleVoteParams = <> "\"drepId\": \"b4e4184bfedf920fec53cdc327de4da661ae427784c0ccca9e3c2f50\"," <> "\"vote\": \"yes\"," <> "\"url\": \"https://vote.metadata.xyz\"," - <> "\"metadataHash\": \"9af10e89979e51b8cdc827c963124a1ef4920d1253eef34a1d5cfe76438e3f11\" }" + <> "\"metadataHash\": \"9af10e89979e51b8cdc827c963124a1ef4920d1253eef34a1d5cfe76438e3f11\"," + <> "\"epochNo\": 0," + <> "\"date\": \"1970-01-01T00:00:00Z\"}" instance ToSchema VoteParams where declareNamedSchema proxy = do @@ -346,10 +402,11 @@ instance ToSchema VoteParams where & example ?~ toJSON exampleVoteParams -data VoteResponse = VoteResponse - { voteResponseVote :: VoteParams, - voteResponseProposal :: ProposalResponse - } +data VoteResponse + = VoteResponse + { voteResponseVote :: VoteParams + , voteResponseProposal :: ProposalResponse + } deriving (Generic, Show) deriveJSON (jsonOptions "voteResponse") ''VoteResponse @@ -374,16 +431,18 @@ instance ToSchema VoteResponse where & example ?~ toJSON exampleVoteResponse -data DRepInfoResponse = DRepInfoResponse - { dRepInfoResponseIsRegisteredAsDRep :: Bool - , dRepInfoResponseWasRegisteredAsDRep :: Bool - , dRepInfoResponseIsRegisteredAsSoleVoter :: Bool - , dRepInfoResponseWasRegisteredAsSoleVoter :: Bool - , dRepInfoResponseDeposit :: Maybe Integer - , dRepInfoResponseUrl :: Maybe Text - , dRepInfoResponseDataHash :: Maybe HexText - , dRepInfoResponseVotingPower :: Maybe Integer - } deriving (Generic, Show) +data DRepInfoResponse + = DRepInfoResponse + { dRepInfoResponseIsRegisteredAsDRep :: Bool + , dRepInfoResponseWasRegisteredAsDRep :: Bool + , dRepInfoResponseIsRegisteredAsSoleVoter :: Bool + , dRepInfoResponseWasRegisteredAsSoleVoter :: Bool + , dRepInfoResponseDeposit :: Maybe Integer + , dRepInfoResponseUrl :: Maybe Text + , dRepInfoResponseDataHash :: Maybe HexText + , dRepInfoResponseVotingPower :: Maybe Integer + } + deriving (Generic, Show) deriveJSON (jsonOptions "dRepInfoResponse") ''DRepInfoResponse @@ -412,10 +471,11 @@ instance ToSchema DRepInfoResponse where ?~ toJSON exampleDRepInfoResponse -data GetProposalResponse = GetProposalResponse - { getProposalResponseVote :: Maybe VoteParams, - getProposalResponseProposal :: ProposalResponse - } +data GetProposalResponse + = GetProposalResponse + { getProposalResponseVote :: Maybe VoteParams + , getProposalResponseProposal :: ProposalResponse + } deriving (Generic, Show) exampleGetProposalResponse :: Text @@ -442,14 +502,15 @@ instance ToSchema GetProposalResponse where ?~ toJSON exampleGetProposalResponse -newtype GetCurrentEpochParamsResponse = GetCurrentEpochParamsResponse { getCurrentEpochParamsResponse :: Maybe Value } +newtype GetCurrentEpochParamsResponse + = GetCurrentEpochParamsResponse { getCurrentEpochParamsResponse :: Maybe Value } deriving newtype (Show) instance FromJSON GetCurrentEpochParamsResponse where parseJSON = pure . GetCurrentEpochParamsResponse . Just instance ToJSON GetCurrentEpochParamsResponse where - toJSON (GetCurrentEpochParamsResponse Nothing) = Null + toJSON (GetCurrentEpochParamsResponse Nothing) = Null toJSON (GetCurrentEpochParamsResponse (Just params)) = toJSON params exampleGetCurrentEpochParamsResponse :: Text @@ -464,9 +525,8 @@ instance ToSchema GetCurrentEpochParamsResponse where ?~ toJSON exampleGetCurrentEpochParamsResponse newtype GetTransactionStatusResponse - = GetTransactionStatusResponse - { getTransactionstatusResponseTransactionConfirmed :: Bool - } deriving (Generic, Show) + = GetTransactionStatusResponse { getTransactionstatusResponseTransactionConfirmed :: Bool } + deriving (Generic, Show) deriveJSON (jsonOptions "getTransactionstatusResponse") ''GetTransactionStatusResponse @@ -489,12 +549,13 @@ instance ToSchema GetTransactionStatusResponse where & example ?~ toJSON exampleGetTransactionStatusResponse -newtype DRepHash = DRepHash Text +newtype DRepHash + = DRepHash Text deriving (Generic, Show) instance FromJSON DRepHash where parseJSON (Aeson.String s) = pure $ DRepHash s - parseJSON x = fail ("expected DRepHash to be a string but got: " <> Char8.unpack (encode x)) + parseJSON x = fail ("expected DRepHash to be a string but got: " <> Char8.unpack (encode x)) instance ToJSON DRepHash where toJSON (DRepHash raw) = toJSON raw @@ -511,22 +572,21 @@ instance ToSchema DRepHash where ?~ toJSON exampleDrepHash -data DRepStatus = Retired | Active | Inactive - deriving (Generic, Show) +data DRepStatus = Retired | Active | Inactive deriving (Generic, Show) -- ToJSON instance for DRepStatus instance ToJSON DRepStatus where - toJSON Retired = "Retired" - toJSON Active = "Active" + toJSON Retired = "Retired" + toJSON Active = "Active" toJSON Inactive = "Inactive" -- FromJSON instance for DRepStatus instance FromJSON DRepStatus where parseJSON = withText "DRepStatus" $ \case - "Retired" -> pure Retired - "Active" -> pure Active + "Retired" -> pure Retired + "Active" -> pure Active "Inactive" -> pure Inactive - _ -> fail "Invalid DRepStatus" + _ -> fail "Invalid DRepStatus" -- ToSchema instance for DRepStatus instance ToSchema DRepStatus where @@ -541,19 +601,19 @@ data DRepType = NormalDRep | SoleVoter instance Show DRepType where show NormalDRep = "DRep" - show SoleVoter = "SoleVoter" + show SoleVoter = "SoleVoter" -- ToJSON instance for DRepType instance ToJSON DRepType where toJSON NormalDRep = "DRep" - toJSON SoleVoter = "SoleVoter" + toJSON SoleVoter = "SoleVoter" -- FromJSON instance for DRepType instance FromJSON DRepType where parseJSON = withText "DRepType" $ \case - "DRep" -> pure NormalDRep + "DRep" -> pure NormalDRep "SoleVoter" -> pure SoleVoter - _ -> fail "Invalid DRepType" + _ -> fail "Invalid DRepType" -- ToSchema instance for DRepType instance ToSchema DRepType where @@ -562,16 +622,18 @@ instance ToSchema DRepType where & description ?~ "DRep Type" & enum_ ?~ map toJSON [NormalDRep, SoleVoter] -data DRep = DRep - { dRepDrepId :: DRepHash - , dRepView :: Text - , dRepUrl :: Maybe Text - , dRepMetadataHash :: Maybe Text - , dRepDeposit :: Integer - , dRepVotingPower :: Maybe Integer - , dRepStatus :: DRepStatus - , dRepType :: DRepType - } deriving (Generic, Show) +data DRep + = DRep + { dRepDrepId :: DRepHash + , dRepView :: Text + , dRepUrl :: Maybe Text + , dRepMetadataHash :: Maybe Text + , dRepDeposit :: Integer + , dRepVotingPower :: Maybe Integer + , dRepStatus :: DRepStatus + , dRepType :: DRepType + } + deriving (Generic, Show) deriveJSON (jsonOptions "dRep") ''DRep @@ -604,18 +666,19 @@ instance ToSchema DRep where -data GetNetworkMetricsResponse = GetNetworkMetricsResponse - { getNetworkMetricsResponseCurrentTime :: UTCTime - , getNetworkMetricsResponseCurrentEpoch :: Integer - , getNetworkMetricsResponseCurrentBlock :: Integer - , getNetworkMetricsResponseUniqueDelegators :: Integer - , getNetworkMetricsResponseTotalDelegations :: Integer - , getNetworkMetricsResponseTotalGovernanceActions :: Integer - , getNetworkMetricsResponseTotalDRepVotes :: Integer - , getNetworkMetricsResponseTotalRegisteredDReps :: Integer - , getNetworkMetricsResponseAlwaysAbstainVotingPower :: Integer - , getNetworkMetricsResponseAlwaysNoConfidenceVotingPower :: Integer - } +data GetNetworkMetricsResponse + = GetNetworkMetricsResponse + { getNetworkMetricsResponseCurrentTime :: UTCTime + , getNetworkMetricsResponseCurrentEpoch :: Integer + , getNetworkMetricsResponseCurrentBlock :: Integer + , getNetworkMetricsResponseUniqueDelegators :: Integer + , getNetworkMetricsResponseTotalDelegations :: Integer + , getNetworkMetricsResponseTotalGovernanceActions :: Integer + , getNetworkMetricsResponseTotalDRepVotes :: Integer + , getNetworkMetricsResponseTotalRegisteredDReps :: Integer + , getNetworkMetricsResponseAlwaysAbstainVotingPower :: Integer + , getNetworkMetricsResponseAlwaysNoConfidenceVotingPower :: Integer + } deriveJSON (jsonOptions "getNetworkMetricsResponse") ''GetNetworkMetricsResponse @@ -637,4 +700,4 @@ instance ToSchema GetNetworkMetricsResponse where & type_ ?~ OpenApiObject & description ?~ "GetNetworkMetricsResponse" & example - ?~ toJSON exampleGetNetworkMetricsResponse \ No newline at end of file + ?~ toJSON exampleGetNetworkMetricsResponse diff --git a/govtool/backend/src/VVA/API/Utils.hs b/govtool/backend/src/VVA/API/Utils.hs index 49a47f672..9ba8f26f8 100644 --- a/govtool/backend/src/VVA/API/Utils.hs +++ b/govtool/backend/src/VVA/API/Utils.hs @@ -1,13 +1,14 @@ module VVA.API.Utils where -import Data.Aeson (Options (..), defaultOptions) -import Data.Char -import Foreign (pooledMalloc) +import Data.Aeson (Options (..), defaultOptions) +import Data.Char + +import Foreign (pooledMalloc) -- | Apply function to first element in the list. applyFirst :: (a -> a) -> [a] -> [a] -applyFirst _ [] = [] -applyFirst f [x] = [f x] +applyFirst _ [] = [] +applyFirst f [x] = [f x] applyFirst f (x : xs) = f x : xs jsonOptions :: String -> Options diff --git a/govtool/backend/src/VVA/AdaHolder.hs b/govtool/backend/src/VVA/AdaHolder.hs index b6766ce67..1e177235f 100644 --- a/govtool/backend/src/VVA/AdaHolder.hs +++ b/govtool/backend/src/VVA/AdaHolder.hs @@ -1,25 +1,29 @@ -{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE TemplateHaskell #-} -{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE TypeApplications #-} module VVA.AdaHolder where -import Control.Monad.Except -import Control.Monad.Reader -import Crypto.Hash -import Data.ByteString (ByteString) -import qualified Data.ByteString.Char8 as C -import Data.FileEmbed (embedFile) -import Data.Scientific -import Data.String (fromString) -import Data.Text (Text, unpack) -import qualified Data.Text.Encoding as Text -import qualified Data.Text.IO as Text +import Control.Monad.Except +import Control.Monad.Reader + +import Crypto.Hash + +import Data.ByteString (ByteString) +import qualified Data.ByteString.Char8 as C +import Data.FileEmbed (embedFile) +import Data.Has (Has) +import Data.Scientific +import Data.String (fromString) +import Data.Text (Text, unpack) +import qualified Data.Text.Encoding as Text +import qualified Data.Text.IO as Text + import qualified Database.PostgreSQL.Simple as SQL -import VVA.Config -import Data.Has (Has) -import VVA.Pool (withPool, ConnectionPool) + +import VVA.Config +import VVA.Pool (ConnectionPool, withPool) sqlFrom :: ByteString -> SQL.Query sqlFrom bs = fromString $ unpack $ Text.decodeUtf8 bs @@ -37,9 +41,9 @@ getCurrentDelegation :: getCurrentDelegation stakeKey = withPool $ \conn -> do result <- liftIO $ SQL.query conn getCurrentDelegationSql (SQL.Only stakeKey) case result of - [] -> return Nothing + [] -> return Nothing [SQL.Only delegation] -> return $ Just delegation - _ -> error ("multiple delegations for stake key: " <> unpack stakeKey) + _ -> error ("multiple delegations for stake key: " <> unpack stakeKey) getVotingPowerSql :: SQL.Query getVotingPowerSql = sqlFrom $(embedFile "sql/get-stake-key-voting-power.sql") @@ -54,4 +58,4 @@ getStakeKeyVotingPower stakeKey = withPool $ \conn -> do [(votingPower,_)] -> return $ floor votingPower _ -> do liftIO $ Text.putStrLn ("couldn't fetch voting power for stake key: " <> stakeKey) - return 0 \ No newline at end of file + return 0 diff --git a/govtool/backend/src/VVA/Cache.hs b/govtool/backend/src/VVA/Cache.hs index 5a5a2fcc6..f3cfe3299 100644 --- a/govtool/backend/src/VVA/Cache.hs +++ b/govtool/backend/src/VVA/Cache.hs @@ -1,10 +1,11 @@ module VVA.Cache where -import qualified Data.Cache as Cache -import Data.Hashable (Hashable) -import Control.Monad.IO.Class (MonadIO, liftIO) -import Data.Text (Text) -import Data.Aeson (Value) +import Control.Monad.IO.Class (MonadIO, liftIO) + +import Data.Aeson (Value) +import qualified Data.Cache as Cache +import Data.Hashable (Hashable) +import Data.Text (Text) cacheRequest :: (Monad m, MonadIO m, Hashable k) => Cache.Cache k v -> k -> m v -> m v @@ -15,4 +16,4 @@ cacheRequest cache key action = do v <- action liftIO $ Cache.insert cache key v return v - Just v -> return v \ No newline at end of file + Just v -> return v diff --git a/govtool/backend/src/VVA/CommandLine.hs b/govtool/backend/src/VVA/CommandLine.hs index 82d28082a..49e9ab94f 100644 --- a/govtool/backend/src/VVA/CommandLine.hs +++ b/govtool/backend/src/VVA/CommandLine.hs @@ -1,18 +1,18 @@ module VVA.CommandLine - ( cmdParser, - CommandLineConfig (..), - Command (..), - ) -where + ( Command (..) + , CommandLineConfig (..) + , cmdParser + ) where -import Options.Applicative +import Options.Applicative data Command = StartApp | ShowConfig deriving (Show) -data CommandLineConfig = CommandLineConfig - { clcConfigPath :: Maybe FilePath, - clcCommand :: Command - } +data CommandLineConfig + = CommandLineConfig + { clcConfigPath :: Maybe FilePath + , clcCommand :: Command + } deriving (Show) cmdParser :: ParserInfo CommandLineConfig diff --git a/govtool/backend/src/VVA/Config.hs b/govtool/backend/src/VVA/Config.hs index 03d0bdd48..7055c33ed 100644 --- a/govtool/backend/src/VVA/Config.hs +++ b/govtool/backend/src/VVA/Config.hs @@ -1,15 +1,12 @@ -{-# LANGUAGE DataKinds #-} -{-# LANGUAGE DeriveAnyClass #-} -{-# LANGUAGE DeriveGeneric #-} -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE GADTs #-} -{-# LANGUAGE LambdaCase #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE PolyKinds #-} -{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE DeriveAnyClass #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE GADTs #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE PolyKinds #-} +{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE TemplateHaskell #-} -{-# LANGUAGE TypeOperators #-} -- | Module : VVA.Config -- Description : Configuration interface @@ -19,65 +16,70 @@ -- configuration backend (Conferer) and ensures that the provided configuration -- is valid and satisfies required invariants. module VVA.Config - ( -- * Data types and basic functions - VVAConfig (..), - loadVVAConfig, - - -- * Data type conversions - vvaConfigToText, - getDbSyncConnectionString, - getServerPort, - getServerHost, - ) -where - -import Conferer -import qualified Conferer.Source.Aeson as JSON -import qualified Conferer.Source.Env as Env -import Control.Monad.Reader -import Data.Aeson + ( -- * Data types and basic functions + VVAConfig (..) + , loadVVAConfig + -- * Data type conversions + , getDbSyncConnectionString + , getServerHost + , getServerPort + , vvaConfigToText + ) where + +import Conferer +import qualified Conferer.Source.Aeson as JSON +import qualified Conferer.Source.Env as Env + +import Control.Monad.Reader + +import Data.Aeson import qualified Data.Aeson.Encode.Pretty as AP -import Data.ByteString (ByteString, toStrict) -import Data.Maybe (fromMaybe) -import Data.Text (Text) -import qualified Data.Text as Text -import Data.Text.Encoding (decodeUtf8, encodeUtf8) -import GHC.Generics -import VVA.CommandLine (CommandLineConfig (..), clcConfigPath) -import Data.Has (Has, getter) +import Data.ByteString (ByteString, toStrict) +import Data.Has (Has, getter) +import Data.Maybe (fromMaybe) +import Data.Text (Text) +import qualified Data.Text as Text +import Data.Text.Encoding (decodeUtf8, encodeUtf8) + +import GHC.Generics + +import VVA.CommandLine (CommandLineConfig (..), + clcConfigPath) -- | PostgreSQL database access information. -data DBConfig = DBConfig - { -- | URL of host running the database - dBConfigHost :: Text, - -- | Database name - dBConfigDbname :: Text, - -- | User name - dBConfigUser :: Text, - -- | Database password - dBConfigPassword :: Text, - -- | Port - dBConfigPort :: Int - } - deriving (Generic, Show, FromConfig) +data DBConfig + = DBConfig + { -- | URL of host running the database + dBConfigHost :: Text + -- | Database name + , dBConfigDbname :: Text + -- | User name + , dBConfigUser :: Text + -- | Database password + , dBConfigPassword :: Text + -- | Port + , dBConfigPort :: Int + } + deriving (FromConfig, Generic, Show) instance DefaultConfig DBConfig where configDef = DBConfig "localhost" "cexplorer" "postgres" "test" 9903 -- | Internal, backend-dependent representation of configuration for DEX. This -- data type should not be exported from this module. -data VVAConfigInternal = VVAConfigInternal - { -- | db-sync database access. - vVAConfigInternalDbsyncconfig :: DBConfig, - -- | Server port. - vVAConfigInternalPort :: Int, - -- | Server host. - vVAConfigInternalHost :: Text, - -- | Request cache duration - vVaConfigInternalCacheDurationSeconds :: Int, - -- | Sentry DSN - vVAConfigInternalSentrydsn :: String - } - deriving (Generic, Show, FromConfig) +data VVAConfigInternal + = VVAConfigInternal + { -- | db-sync database access. + vVAConfigInternalDbsyncconfig :: DBConfig + -- | Server port. + , vVAConfigInternalPort :: Int + -- | Server host. + , vVAConfigInternalHost :: Text + -- | Request cache duration + , vVaConfigInternalCacheDurationSeconds :: Int + -- | Sentry DSN + , vVAConfigInternalSentrydsn :: String + } + deriving (FromConfig, Generic, Show) instance DefaultConfig VVAConfigInternal where configDef = @@ -90,19 +92,20 @@ instance DefaultConfig VVAConfigInternal where } -- | DEX configuration. -data VVAConfig = VVAConfig - { -- | db-sync database credentials. - dbSyncConnectionString :: Text, - -- | Server port. - serverPort :: Int, - -- | Server host. - serverHost :: Text, - -- | Request cache duration - cacheDurationSeconds :: Int, - -- | Sentry DSN - sentryDSN :: String - } - deriving (Generic, ToJSON, Show) +data VVAConfig + = VVAConfig + { -- | db-sync database credentials. + dbSyncConnectionString :: Text + -- | Server port. + , serverPort :: Int + -- | Server host. + , serverHost :: Text + -- | Request cache duration + , cacheDurationSeconds :: Int + -- | Sentry DSN + , sentryDSN :: String + } + deriving (Generic, Show, ToJSON) -- | Convert 'DBConfig' to a string required by PostgreSQL backend. dbConfigToText :: DBConfig -> Text @@ -165,7 +168,7 @@ loadVVAConfig configFile = do getDbSyncConnectionString :: (Has VVAConfig r, MonadReader r m) => m ByteString -getDbSyncConnectionString = encodeUtf8 <$> asks (dbSyncConnectionString . getter) +getDbSyncConnectionString = asks (encodeUtf8 . dbSyncConnectionString . getter) -- | Access server port. getServerPort :: diff --git a/govtool/backend/src/VVA/DRep.hs b/govtool/backend/src/VVA/DRep.hs index 211119a8e..eede66a81 100644 --- a/govtool/backend/src/VVA/DRep.hs +++ b/govtool/backend/src/VVA/DRep.hs @@ -1,42 +1,39 @@ -{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE RecordWildCards #-} -{-# LANGUAGE TemplateHaskell #-} -{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE TypeApplications #-} module VVA.DRep where -import Control.Monad.Except (MonadError) -import Control.Monad.Reader -import Crypto.Hash -import Data.Maybe (fromMaybe) -import Data.ByteString (ByteString) -import qualified Data.ByteString.Base16 as Base16 -import qualified Data.ByteString.Char8 as C -import Data.FileEmbed (embedFile) -import qualified Data.Map as M -import Data.Scientific -import Data.String (fromString) -import Data.Text (Text, unpack, pack) -import qualified Data.Text.Encoding as Text -import qualified Database.PostgreSQL.Simple as SQL -import VVA.Config -import qualified VVA.Proposal as Proposal -import Data.Foldable (Foldable(sum)) -import Data.Has (Has) -import VVA.Pool (ConnectionPool, withPool) -import VVA.Types - ( AppError - , DRepRegistration(..) - , Proposal(..) - , Vote(..) - , DRepInfo(..) - , DRepType(..) - , DRepStatus(..) - ) +import Control.Monad.Except (MonadError) +import Control.Monad.Reader + +import Crypto.Hash +import Data.ByteString (ByteString) +import qualified Data.ByteString.Base16 as Base16 +import qualified Data.ByteString.Char8 as C +import Data.FileEmbed (embedFile) +import Data.Foldable (Foldable (sum)) +import Data.Has (Has) +import qualified Data.Map as M +import Data.Maybe (fromMaybe, isJust, isNothing) +import Data.Scientific +import Data.String (fromString) +import Data.Text (Text, pack, unpack) +import qualified Data.Text.Encoding as Text +import Data.Time +import qualified Database.PostgreSQL.Simple as SQL +import VVA.Config +import VVA.Pool (ConnectionPool, withPool) +import qualified VVA.Proposal as Proposal +import VVA.Types (AppError, DRepInfo (..), + DRepRegistration (..), + DRepStatus (..), DRepType (..), + Proposal (..), Vote (..)) sqlFrom :: ByteString -> SQL.Query sqlFrom bs = fromString $ unpack $ Text.decodeUtf8 bs @@ -69,9 +66,9 @@ listDReps = withPool $ \conn -> do (_, d) | d < 0 -> Retired (isActive, d) | d >= 0 && isActive -> Active | d >= 0 && not isActive -> Inactive - , let drepType | url == Nothing && wasDRep = DRep - | url == Nothing && not wasDRep = SoleVoter - | url /= Nothing = DRep + , let drepType | isNothing url && wasDRep = DRep + | isNothing url && not wasDRep = SoleVoter + | Data.Maybe.isJust url = DRep ] getVotesSql :: SQL.Query @@ -85,13 +82,14 @@ getVotes :: getVotes drepId selectedProposals = withPool $ \conn -> do results <- liftIO $ SQL.query conn getVotesSql (SQL.Only drepId) let proposalsToSelect = if null selectedProposals - then [ govActionId | (_, govActionId, _, _, _, _) <- results] + then [ govActionId | (_, govActionId, _, _, _, _, _, _) <- results] else selectedProposals proposals <- Proposal.getProposals (Just proposalsToSelect) let proposalMap = M.fromList $ map (\x -> (proposalId x, x)) proposals + timeZone <- liftIO getCurrentTimeZone return - ([ Vote proposalId' drepId' vote' url' docHash' - | (proposalId', govActionId', drepId', vote', url', docHash') <- results + ([ Vote proposalId' drepId' vote' url' docHash' epochNo' (localTimeToUTC timeZone date') + | (proposalId', govActionId', drepId', vote', url', docHash', epochNo', date') <- results , govActionId' `elem` proposalsToSelect ], proposals) @@ -130,4 +128,4 @@ getDRepInfo drepId = withPool $ \conn -> do , dRepInfoDataHash = dataHash , dRepInfoVotingPower = votingPower } - [] -> return $ DRepInfo False False False False Nothing Nothing Nothing Nothing \ No newline at end of file + [] -> return $ DRepInfo False False False False Nothing Nothing Nothing Nothing diff --git a/govtool/backend/src/VVA/Epoch.hs b/govtool/backend/src/VVA/Epoch.hs index f29819038..9c48db028 100644 --- a/govtool/backend/src/VVA/Epoch.hs +++ b/govtool/backend/src/VVA/Epoch.hs @@ -1,22 +1,24 @@ +{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE TemplateHaskell #-} -{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE TemplateHaskell #-} module VVA.Epoch where +import Control.Monad.Except (MonadError, throwError) +import Control.Monad.Reader + +import Data.Aeson (Value) +import Data.ByteString (ByteString) +import Data.FileEmbed (embedFile) +import Data.Has (Has) +import Data.String (fromString) +import Data.Text (Text, unpack) +import qualified Data.Text.Encoding as Text -import Data.ByteString (ByteString) -import Control.Monad.Except (MonadError, throwError) -import Control.Monad.Reader -import Data.FileEmbed (embedFile) -import Data.Text (Text, unpack) -import Data.String (fromString) -import qualified Data.Text.Encoding as Text import qualified Database.PostgreSQL.Simple as SQL -import VVA.Config -import Data.Aeson (Value) -import Data.Has (Has) -import VVA.Pool (ConnectionPool, withPool) + +import VVA.Config +import VVA.Pool (ConnectionPool, withPool) sqlFrom :: ByteString -> SQL.Query sqlFrom bs = fromString $ unpack $ Text.decodeUtf8 bs @@ -30,5 +32,5 @@ getCurrentEpochParams :: getCurrentEpochParams = withPool $ \conn -> do result <- liftIO $ SQL.query_ conn getCurrentEpochParamsSql case result of - [] -> return Nothing + [] -> return Nothing (SQL.Only params:_) -> return $ Just params diff --git a/govtool/backend/src/VVA/Network.hs b/govtool/backend/src/VVA/Network.hs index a76be9452..2329c689e 100644 --- a/govtool/backend/src/VVA/Network.hs +++ b/govtool/backend/src/VVA/Network.hs @@ -1,24 +1,26 @@ +{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE TemplateHaskell #-} -{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE TemplateHaskell #-} module VVA.Network where +import Control.Monad.Except (MonadError, throwError) +import Control.Monad.Reader + +import Data.Aeson (Value) +import Data.ByteString (ByteString) +import Data.FileEmbed (embedFile) +import Data.Has (Has) +import Data.String (fromString) +import Data.Text (Text, unpack) +import qualified Data.Text.Encoding as Text +import Data.Time.Clock -import Data.ByteString (ByteString) -import Control.Monad.Except (MonadError, throwError) -import Control.Monad.Reader -import Data.FileEmbed (embedFile) -import Data.Text (Text, unpack) -import Data.String (fromString) -import qualified Data.Text.Encoding as Text import qualified Database.PostgreSQL.Simple as SQL -import VVA.Config -import Data.Aeson (Value) -import Data.Has (Has) -import VVA.Pool (ConnectionPool, withPool) -import VVA.Types -import Data.Time.Clock + +import VVA.Config +import VVA.Pool (ConnectionPool, withPool) +import VVA.Types sqlFrom :: ByteString -> SQL.Query sqlFrom bs = fromString $ unpack $ Text.decodeUtf8 bs @@ -31,7 +33,7 @@ networkMetrics :: m NetworkMetrics networkMetrics = withPool $ \conn -> do result <- liftIO $ SQL.query_ conn networkMetricsSql - current_time <- liftIO $ getCurrentTime + current_time <- liftIO getCurrentTime case result of [( epoch_no , block_no diff --git a/govtool/backend/src/VVA/Pool.hs b/govtool/backend/src/VVA/Pool.hs index 5ecfa0a34..0431d596b 100644 --- a/govtool/backend/src/VVA/Pool.hs +++ b/govtool/backend/src/VVA/Pool.hs @@ -1,11 +1,14 @@ {-# LANGUAGE FlexibleContexts #-} + module VVA.Pool where -import Data.Has (Has, getter) -import Data.Pool (Pool, takeResource, putResource) -import Database.PostgreSQL.Simple (Connection) -import Control.Monad.Reader (MonadReader, asks) -import Control.Monad.IO.Class (MonadIO, liftIO) +import Control.Monad.IO.Class (MonadIO, liftIO) +import Control.Monad.Reader (MonadReader, asks) + +import Data.Has (Has, getter) +import Data.Pool (Pool, putResource, takeResource) + +import Database.PostgreSQL.Simple (Connection) type ConnectionPool = Pool Connection @@ -18,4 +21,4 @@ withPool f = do (conn,localPool) <- liftIO $ takeResource pool result <- f conn liftIO $ putResource localPool conn - return result \ No newline at end of file + return result diff --git a/govtool/backend/src/VVA/Proposal.hs b/govtool/backend/src/VVA/Proposal.hs index d47e7af50..e2c949992 100644 --- a/govtool/backend/src/VVA/Proposal.hs +++ b/govtool/backend/src/VVA/Proposal.hs @@ -1,36 +1,41 @@ -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE RecordWildCards #-} -{-# LANGUAGE TemplateHaskell #-} -{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE TypeApplications #-} module VVA.Proposal where -import Control.Monad.Except (MonadError, throwError) -import Control.Monad.Reader -import Data.ByteString (ByteString) -import Data.FileEmbed (embedFile) -import Data.Foldable (fold) -import qualified Data.Map as Map -import Data.Maybe (fromMaybe) -import Data.Monoid (Sum (..), getSum) -import Data.Scientific -import Data.String (fromString) -import Data.Text (Text, unpack, pack) -import qualified Data.Text.Encoding as Text -import qualified Data.Text.IO as Text -import Data.Time +import Control.Exception (throw) +import Control.Monad.Except (MonadError, throwError) +import Control.Monad.Reader + +import Data.Aeson +import Data.Aeson.Types (Parser, parseMaybe) +import Data.ByteString (ByteString) +import Data.FileEmbed (embedFile) +import Data.Foldable (fold) +import Data.Has (Has) +import qualified Data.Map as Map +import Data.Maybe (fromMaybe) +import Data.Monoid (Sum (..), getSum) +import Data.Scientific +import Data.String (fromString) +import Data.Text (Text, pack, unpack) +import qualified Data.Text.Encoding as Text +import qualified Data.Text.IO as Text +import Data.Time + import qualified Database.PostgreSQL.Simple as SQL -import qualified GHC.Generics as SQL -import VVA.Config -import Data.Aeson (Value) -import Text.Read (readMaybe) -import Data.Has (Has) -import VVA.Pool (ConnectionPool, withPool) -import Control.Exception (throw) -import VVA.Types (Proposal(..), AppError(..)) +import qualified GHC.Generics as SQL + +import Text.Read (readMaybe) + +import VVA.Config +import VVA.Pool (ConnectionPool, withPool) +import VVA.Types (AppError (..), Proposal (..)) sqlFrom :: ByteString -> SQL.Query sqlFrom bs = fromString $ unpack $ Text.decodeUtf8 bs @@ -55,7 +60,6 @@ getProposal txHash index = do [a] -> return a _ -> throwError $ CriticalError ("Multiple proposal found for id: " <> txHash <> "#" <> pack (show index) <> ". This should never happen") - getProposals :: (Has ConnectionPool r, Has VVAConfig r, MonadReader r m, MonadIO m, MonadFail m, MonadError AppError m) => Maybe [Text] -> @@ -67,10 +71,48 @@ getProposals mProposalIds = withPool $ \conn -> do timeZone <- liftIO getCurrentTimeZone return $ map - ( \(id', txHash', index', type', details', expiryDate', createdDate', url', docHash', yesVotes', noVotes', abstainVotes') -> + ( \( id' + , txHash' + , index' + , type' + , details' + , expiryDate' + , expiryEpochNo' + , createdDate' + , createdEpochNo' + , url' + , docHash' + , title' + , about' + , motivation' + , rationale' + , metadataJson' + , yesVotes' + , noVotes' + , abstainVotes' + ) -> let eDate = localTimeToUTC timeZone <$> expiryDate' cDate = localTimeToUTC timeZone createdDate' in - Proposal id' txHash' (floor @Scientific index') type' details' eDate cDate url' docHash' (floor @Scientific yesVotes') (floor @Scientific noVotes') (floor @Scientific abstainVotes') + Proposal + id' + txHash' + (floor @Scientific index') + type' + details' + eDate + expiryEpochNo' + cDate + createdEpochNo' + url' + docHash' + title' + about' + motivation' + rationale' + metadataJson' + (floor @Scientific yesVotes') + (floor @Scientific noVotes') + (floor @Scientific abstainVotes') ) proposalResults diff --git a/govtool/backend/src/VVA/Transaction.hs b/govtool/backend/src/VVA/Transaction.hs index a67cb1371..73ebc594a 100644 --- a/govtool/backend/src/VVA/Transaction.hs +++ b/govtool/backend/src/VVA/Transaction.hs @@ -1,29 +1,31 @@ +{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE TemplateHaskell #-} -{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE TemplateHaskell #-} module VVA.Transaction where +import Control.Exception (throw) +import Control.Monad.Except (MonadError, throwError) +import Control.Monad.Reader + +import Data.Aeson (Value) +import Data.ByteString (ByteString) +import Data.FileEmbed (embedFile) +import Data.Has (Has) +import Data.String (fromString) +import Data.Text (Text, pack, unpack) +import qualified Data.Text.Encoding as Text -import Data.ByteString (ByteString) -import Control.Monad.Except (MonadError, throwError) -import Control.Monad.Reader -import Data.FileEmbed (embedFile) -import Data.Text (Text, unpack, pack) -import Data.String (fromString) -import qualified Data.Text.Encoding as Text import qualified Database.PostgreSQL.Simple as SQL -import VVA.Config -import Data.Aeson (Value) -import Data.Has (Has) -import VVA.Pool (ConnectionPool, withPool) -import VVA.Types (TransactionStatus(..), AppError(..)) -import Control.Exception (throw) + +import VVA.Config +import VVA.Pool (ConnectionPool, withPool) +import VVA.Types (AppError (..), + TransactionStatus (..)) sqlFrom :: ByteString -> SQL.Query sqlFrom bs = fromString $ unpack $ Text.decodeUtf8 bs - getTransactionStatusSql :: SQL.Query getTransactionStatusSql = sqlFrom $(embedFile "sql/get-transaction-status.sql") @@ -36,4 +38,4 @@ getTransactionStatus transactionId = withPool $ \conn -> do case result of [SQL.Only True] -> return TransactionConfirmed [SQL.Only False] -> return TransactionUnconfirmed - x -> throwError $ CriticalError ("Expected exactly one result from get-transaction-status.sql but got " <> pack (show (length x)) <> " of them. This should never happen") \ No newline at end of file + x -> throwError $ CriticalError ("Expected exactly one result from get-transaction-status.sql but got " <> pack (show (length x)) <> " of them. This should never happen") diff --git a/govtool/backend/src/VVA/Types.hs b/govtool/backend/src/VVA/Types.hs index e316f34e0..15ac3595f 100644 --- a/govtool/backend/src/VVA/Types.hs +++ b/govtool/backend/src/VVA/Types.hs @@ -1,33 +1,37 @@ -{-# LANGUAGE NamedFieldPuns #-} +{-# LANGUAGE ConstraintKinds #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} -{-# LANGUAGE FlexibleInstances #-} -{-# LANGUAGE ConstraintKinds #-} -{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE NamedFieldPuns #-} module VVA.Types where -import Data.Text (Text) -import Control.Exception -import Control.Monad.IO.Class (MonadIO) -import Control.Monad.Reader (MonadReader) -import Control.Monad.Fail (MonadFail) -import Control.Monad.Except (MonadError) -import VVA.Config -import VVA.Cache -import Data.Has -import Data.Pool (Pool) -import Database.PostgreSQL.Simple (Connection) -import Data.Aeson (Value) -import Data.Time (UTCTime) -import qualified Data.Cache as Cache +import Control.Exception +import Control.Monad.Except (MonadError) +import Control.Monad.Fail (MonadFail) +import Control.Monad.IO.Class (MonadIO) +import Control.Monad.Reader (MonadReader) + +import Data.Aeson (Value) +import qualified Data.Cache as Cache +import Data.Has +import Data.Pool (Pool) +import Data.Text (Text) +import Data.Time (UTCTime) + +import Database.PostgreSQL.Simple (Connection) + +import VVA.Cache +import VVA.Config type App m = (MonadReader AppEnv m, MonadIO m, MonadFail m, MonadError AppError m) -data AppEnv = AppEnv - { vvaConfig :: VVAConfig - , vvaCache :: CacheEnv - , vvaConnectionPool :: Pool Connection - } +data AppEnv + = AppEnv + { vvaConfig :: VVAConfig + , vvaCache :: CacheEnv + , vvaConnectionPool :: Pool Connection + } instance Has VVAConfig AppEnv where getter AppEnv {vvaConfig} = vvaConfig @@ -42,97 +46,102 @@ instance Has (Pool Connection) AppEnv where modifier f a@AppEnv {vvaConnectionPool} = a {vvaConnectionPool = f vvaConnectionPool} data AppError - = ValidationError Text - | NotFoundError Text - | CriticalError Text - deriving Show - + = ValidationError Text + | NotFoundError Text + | CriticalError Text + deriving (Show) instance Exception AppError -data Vote = Vote - { voteProposalId :: Integer, - voteDrepId :: Text, - voteVote :: Text, - voteUrl :: Maybe Text, - voteDocHash :: Maybe Text - } - -data DRepInfo = DRepInfo - { dRepInfoIsRegisteredAsDRep :: Bool - , dRepInfoWasRegisteredAsDRep :: Bool - , dRepInfoIsRegisteredAsSoleVoter :: Bool - , dRepInfoWasRegisteredAsSoleVoter :: Bool - , dRepInfoDeposit :: Maybe Integer - , dRepInfoUrl :: Maybe Text - , dRepInfoDataHash :: Maybe Text - , dRepInfoVotingPower :: Maybe Integer - } - -data DRepStatus - = Retired - | Active - | Inactive - -data DRepType - = DRep - | SoleVoter - -data DRepRegistration = DRepRegistration - { dRepRegistrationDRepHash :: Text - , dRepRegistrationView :: Text - , dRepRegistrationUrl :: Maybe Text - , dRepRegistrationDataHash :: Maybe Text - , dRepRegistrationDeposit :: Integer - , dRepRegistrationVotingPower :: Maybe Integer - , dRepRegistrationStatus :: DRepStatus - , dRepRegistrationType :: DRepType - } - -data Proposal = Proposal - { proposalId :: Integer, - proposalTxHash :: Text, - proposalIndex :: Integer, - proposalType :: Text, - proposalDetails :: Value, - proposalExpiryDate :: Maybe UTCTime, - proposalCreatedDate :: UTCTime, - proposalUrl :: Text, - proposalDocHash :: Text, - proposalYesVotes :: Integer, - proposalNoVotes :: Integer, - proposalAbstainVotes :: Integer - } deriving (Show) - - - -data TransactionStatus - = TransactionConfirmed - | TransactionUnconfirmed - -data CacheEnv = CacheEnv - { proposalListCache :: Cache.Cache () [Proposal] - , getProposalCache :: Cache.Cache (Text, Integer) Proposal - , currentEpochCache :: Cache.Cache () (Maybe Value) - , adaHolderVotingPowerCache :: Cache.Cache Text Integer - , adaHolderGetCurrentDelegationCache :: Cache.Cache Text (Maybe Text) - , dRepGetVotesCache :: Cache.Cache Text ([Vote], [Proposal]) - , dRepInfoCache :: Cache.Cache Text DRepInfo - , dRepVotingPowerCache :: Cache.Cache Text Integer - , dRepListCache :: Cache.Cache () [DRepRegistration] - , networkMetricsCache :: Cache.Cache () NetworkMetrics - } - -data NetworkMetrics = NetworkMetrics - { networkMetricsCurrentTime :: UTCTime - , networkMetricsCurrentEpoch :: Integer - , networkMetricsCurrentBlock :: Integer - , networkMetricsUniqueDelegators :: Integer - , networkMetricsTotalDelegations :: Integer - , networkMetricsTotalGovernanceActions :: Integer - , networkMetricsTotalDRepVotes :: Integer - , networkMetricsTotalRegisteredDReps :: Integer - , networkMetricsAlwaysAbstainVotingPower :: Integer - , networkMetricsAlwaysNoConfidenceVotingPower :: Integer - } - +data Vote + = Vote + { voteProposalId :: Integer + , voteDrepId :: Text + , voteVote :: Text + , voteUrl :: Maybe Text + , voteDocHash :: Maybe Text + , voteEpochNo :: Integer + , voteDate :: UTCTime + } + +data DRepInfo + = DRepInfo + { dRepInfoIsRegisteredAsDRep :: Bool + , dRepInfoWasRegisteredAsDRep :: Bool + , dRepInfoIsRegisteredAsSoleVoter :: Bool + , dRepInfoWasRegisteredAsSoleVoter :: Bool + , dRepInfoDeposit :: Maybe Integer + , dRepInfoUrl :: Maybe Text + , dRepInfoDataHash :: Maybe Text + , dRepInfoVotingPower :: Maybe Integer + } + +data DRepStatus = Retired | Active | Inactive + +data DRepType = DRep | SoleVoter + +data DRepRegistration + = DRepRegistration + { dRepRegistrationDRepHash :: Text + , dRepRegistrationView :: Text + , dRepRegistrationUrl :: Maybe Text + , dRepRegistrationDataHash :: Maybe Text + , dRepRegistrationDeposit :: Integer + , dRepRegistrationVotingPower :: Maybe Integer + , dRepRegistrationStatus :: DRepStatus + , dRepRegistrationType :: DRepType + } + +data Proposal + = Proposal + { proposalId :: Integer + , proposalTxHash :: Text + , proposalIndex :: Integer + , proposalType :: Text + , proposalDetails :: Maybe Value + , proposalExpiryDate :: Maybe UTCTime + , proposalExpiryEpochNo :: Maybe Integer + , proposalCreatedDate :: UTCTime + , proposalCreatedEpochNo :: Integer + , proposalUrl :: Text + , proposalDocHash :: Text + , proposalTitle :: Maybe Text + , proposalAbout :: Maybe Text + , proposalMotivaiton :: Maybe Text + , proposalRationale :: Maybe Text + , proposalMetadata :: Maybe Value + , proposalYesVotes :: Integer + , proposalNoVotes :: Integer + , proposalAbstainVotes :: Integer + } + deriving (Show) + +data TransactionStatus = TransactionConfirmed | TransactionUnconfirmed + +data CacheEnv + = CacheEnv + { proposalListCache :: Cache.Cache () [Proposal] + , getProposalCache :: Cache.Cache (Text, Integer) Proposal + , currentEpochCache :: Cache.Cache () (Maybe Value) + , adaHolderVotingPowerCache :: Cache.Cache Text Integer + , adaHolderGetCurrentDelegationCache :: Cache.Cache Text (Maybe Text) + , dRepGetVotesCache :: Cache.Cache Text ([Vote], [Proposal]) + , dRepInfoCache :: Cache.Cache Text DRepInfo + , dRepVotingPowerCache :: Cache.Cache Text Integer + , dRepListCache :: Cache.Cache () [DRepRegistration] + , networkMetricsCache :: Cache.Cache () NetworkMetrics + } + +data NetworkMetrics + = NetworkMetrics + { networkMetricsCurrentTime :: UTCTime + , networkMetricsCurrentEpoch :: Integer + , networkMetricsCurrentBlock :: Integer + , networkMetricsUniqueDelegators :: Integer + , networkMetricsTotalDelegations :: Integer + , networkMetricsTotalGovernanceActions :: Integer + , networkMetricsTotalDRepVotes :: Integer + , networkMetricsTotalRegisteredDReps :: Integer + , networkMetricsAlwaysAbstainVotingPower :: Integer + , networkMetricsAlwaysNoConfidenceVotingPower :: Integer + } diff --git a/govtool/backend/stack.yaml b/govtool/backend/stack.yaml new file mode 100644 index 000000000..b6238c401 --- /dev/null +++ b/govtool/backend/stack.yaml @@ -0,0 +1,6 @@ +resolver: lts-20.24 +packages: + - . + +extra-deps: +- raven-haskell-0.1.4.1@sha256:9187272adc064197528645b5ad9b89163b668f386f34016d97fa646d5c790784 diff --git a/govtool/backend/stack.yaml.lock b/govtool/backend/stack.yaml.lock new file mode 100644 index 000000000..985ab39a4 --- /dev/null +++ b/govtool/backend/stack.yaml.lock @@ -0,0 +1,19 @@ +# This file was autogenerated by Stack. +# You should not edit this file by hand. +# For more information, please see the documentation at: +# https://docs.haskellstack.org/en/stable/lock_files + +packages: +- completed: + hackage: raven-haskell-0.1.4.1@sha256:9187272adc064197528645b5ad9b89163b668f386f34016d97fa646d5c790784,1479 + pantry-tree: + sha256: e8f14bfed6b055dc95933257441ba97d3d779cbe08a0e82c3c2dff5d69c8c48f + size: 632 + original: + hackage: raven-haskell-0.1.4.1@sha256:9187272adc064197528645b5ad9b89163b668f386f34016d97fa646d5c790784 +snapshots: +- completed: + sha256: e019cd29e3f7f9dbad500225829a3f7a50f73c674614f2f452e21bb8bf5d99ea + size: 650253 + url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/20/24.yaml + original: lts-20.24 diff --git a/govtool/backend/vva-be.cabal b/govtool/backend/vva-be.cabal index e126e1aa2..ccd247dc4 100644 --- a/govtool/backend/vva-be.cabal +++ b/govtool/backend/vva-be.cabal @@ -60,11 +60,13 @@ executable vva-be , lens , cache , clock - , resource-pool >= 0.4.0.0 + , resource-pool == 0.2.3.2 , postgresql-simple , data-has - , raven-haskell >= 0.1.4.1 , bytestring + , http-client + , http-client-tls + , raven-haskell >= 0.1.4.1 hs-source-dirs: app default-language: Haskell2010 @@ -97,6 +99,7 @@ library , resource-pool , swagger2 + exposed-modules: VVA.Config , VVA.CommandLine , VVA.API @@ -110,4 +113,4 @@ library , VVA.Cache , VVA.Pool , VVA.Types - , VVA.Network \ No newline at end of file + , VVA.Network diff --git a/govtool/frontend/.env.example b/govtool/frontend/.env.example deleted file mode 100644 index cacf4a05a..000000000 --- a/govtool/frontend/.env.example +++ /dev/null @@ -1,5 +0,0 @@ -VITE_BASE_URL= -VITE_NETWORK_FLAG= -VITE_SENTRY_DSN= -VITE_GTM_ID= -VITE_IS_DEV= \ No newline at end of file diff --git a/govtool/frontend/.envrc b/govtool/frontend/.envrc new file mode 100644 index 000000000..887f4dc16 --- /dev/null +++ b/govtool/frontend/.envrc @@ -0,0 +1,17 @@ +# vim: set ft=bash + +source_up +watch_file default.nix yarn.lock +env_vars_required CARDANO_NETWORK GTM_ID SENTRY_DSN + +if [[ "${CARDANO_NETWORK}" = "mainnet" ]]; then + export VITE_NETWORK_FLAG=1 +else + export VITE_NETWORK_FLAG=0 +fi +export VITE_BASE_URL=http://localhost +export VITE_IS_DEV=true +export VITE_GTM_ID="${GTM_ID}" +export VITE_SENTRY_DSN="${SENTRY_DSN}" + +use flake --extra-experimental-features nix-command --extra-experimental-features flakes ../..#frontend diff --git a/govtool/frontend/.eslintrc.cjs b/govtool/frontend/.eslintrc.cjs new file mode 100644 index 000000000..07d27ff79 --- /dev/null +++ b/govtool/frontend/.eslintrc.cjs @@ -0,0 +1,109 @@ +module.exports = { + env: { + browser: true, + es2021: true, + }, + extends: [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended", + "plugin:storybook/recommended", + "plugin:react/recommended", + "plugin:react-hooks/recommended", + "plugin:jest/recommended", + "airbnb", + ], + plugins: ["@typescript-eslint", "react", "jest"], + parser: "@typescript-eslint/parser", + parserOptions: { + tsconfigRootDir: __dirname, + ecmaFeatures: { + jsx: true, + }, + ecmaVersion: 2018, + sourceType: "module", + project: "./tsconfig.json", + files: ["*.ts", "*.tsx"], + }, + rules: { + "array-callback-return": "off", + curly: "off", + indent: "off", + quotes: "off", + semi: ["error", "always"], + "linebreak-style": ["error", "unix"], + "no-redeclare": "off", + "no-undef": "off", + "no-prototype-builtins": "off", + "nonblock-statement-body-position": "off", + + // TODO: This rule should be enabled in the future + "no-param-reassign": "off", + + // TODO: This rule should be enabled in the future + "no-plusplus": "off", + + // TODO: This rule should be enabled in the future + "no-nested-ternary": "off", + + "no-unused-vars": "off", + "no-use-before-define": "off", + "comma-dangle": "off", + "operator-linebreak": "off", + "implicit-arrow-linebreak": "off", + "consistent-return": "off", + "no-console": ["error", { allow: ["warn", "error"] }], + "no-shadow": "off", + "function-paren-newline": "off", + "object-curly-newline": "off", + + "@typescript-eslint/no-shadow": ["error"], + "@typescript-eslint/no-redeclare": ["error"], + "@typescript-eslint/no-unused-vars": ["error"], + + "import/prefer-default-export": "off", + "import/extensions": "off", + "import/no-unresolved": "off", + "import/no-extraneous-dependencies": [ + "error", + { + devDependencies: ["**/*.stories.{ts,tsx}", "**/*.test.{ts,tsx}"], + }, + ], + + "jsx-a11y/anchor-is-valid": "off", + "jsx-a11y/no-noninteractive-element-interactions": "off", + "jsx-a11y/click-events-have-key-events": "off", + + "react/display-name": "off", + "react/jsx-curly-newline": "off", + "react/jsx-filename-extension": ["error", { extensions: [".tsx"] }], + "react/jsx-fragments": "off", + "react/jsx-no-bind": "warn", + "react/jsx-no-useless-fragment": "off", + "react/jsx-wrap-multilines": "off", + "react/jsx-props-no-spreading": "off", + "react/jsx-uses-react": "error", + "react/jsx-one-expression-per-line": "off", + "react/prop-types": "off", + "react/react-in-jsx-scope": "off", + "react/function-component-definition": [ + "error", + { + namedComponents: "arrow-function", + unnamedComponents: "arrow-function", + }, + ], + "react/require-default-props": "off", + + // TODO: This rule should be enabled in the future + "react-hooks/exhaustive-deps": "off", + }, + ignorePatterns: [ + ".eslintrc.cjs", + ".storybook/", + ".vite/", + "dist/", + "node_modules/", + "vite.config.ts", + ], +}; diff --git a/govtool/frontend/.lighthouserc.yml b/govtool/frontend/.lighthouserc.yml index 1f13674c0..5e963f7ae 100644 --- a/govtool/frontend/.lighthouserc.yml +++ b/govtool/frontend/.lighthouserc.yml @@ -2,4 +2,4 @@ ci: collect: staticDistDir: "./dist" url: - - "http://localhost" \ No newline at end of file + - "http://localhost" diff --git a/govtool/frontend/.prettierrc b/govtool/frontend/.prettierrc new file mode 100644 index 000000000..f52db5fca --- /dev/null +++ b/govtool/frontend/.prettierrc @@ -0,0 +1,6 @@ +{ + "semi": true, + "singleQuote": false, + "trailingComma": "all", + "printWidth": 80 +} diff --git a/govtool/frontend/Dockerfile b/govtool/frontend/Dockerfile index e0755d481..4e06933bf 100644 --- a/govtool/frontend/Dockerfile +++ b/govtool/frontend/Dockerfile @@ -1,5 +1,5 @@ FROM node:18-alpine as builder -ARG VITE_BASE_URL=vv-be.localhost +ARG VITE_BASE_URL ARG VITE_GTM_ID ARG VITE_NETWORK_FLAG=0 ARG VITE_SENTRY_DSN diff --git a/govtool/frontend/README.md b/govtool/frontend/README.md index d8ace9d3b..b66001800 100644 --- a/govtool/frontend/README.md +++ b/govtool/frontend/README.md @@ -2,13 +2,13 @@ Installed on your machine: -1. Node.js >= 16 ([official website](https://nodejs.org/en)) -2. npm or yarn - for package management +1. Node.js >= 18 ([official website](https://nodejs.org/en)) +2. npm or yarn (recommended) - for package management Clone the project ```bash - git clone https://github.com/IntersectMBO/govtool +git clone https://github.com/IntersectMBO/govtool ``` Fill .env based on env.example file @@ -16,19 +16,19 @@ Fill .env based on env.example file Go to the project directory ```bash - cd voltaire-era/govtool/frontend +cd govtool/frontend ``` Install dependencies ```bash - npm install +npm install ``` -or +or (recommended) ```bash - yarn install +yarn install ``` Start the server @@ -37,53 +37,12 @@ Start the server npm run dev ``` -or +or (recommended) ```bash yarn dev ``` -#### Using nix - -1. Get [Nix](https://nixos.org/download). - -2. Enter `govtool/frontend` directory: - -```sh -cd govtool/frontend -``` - -3. Run `nix-shell` - -```sh -nix-shell -``` - -4. Run project - -```sh -npm run dev -``` - -##### Alternative - -You can skip point 3 by using an automatic direnv configuration: - -Install [`direnv`](https://direnv.net/): - -```sh -nix-env -i direnv -``` - -Allow direnv configuration in `govtool/frontend`: - -```sh -direnv allow govtool/frontend -``` - -From now on, once you enter the `govtool/frontend` the `nix-shell` with proper -configuration will be propagated. - ## Developing ### Frontend Built With @@ -100,13 +59,30 @@ configuration will be propagated. 10. Yup - ^1.3.2 11. Keen-Slider - ^6.8.5 12. Sentry - ^7.77.0 -13. Cardano serialization lib - 12.0.0-alpha.13 +13. Cardano serialization lib - 12.0.0-alpha.19 +14. i18next - ^23.7.19 + +### Code Quality and Checks Are Handled BY + +1. eslint - ^8.38.0 +2. vitest - ^1.1.0 +3. chromatic - ^10.0.0 ### Prerequisites -Install [`Git`](https://git-scm.com/) - version control -Recommendetd [`React developer tools`](https://react.dev/learn/react-developer-tools) +Install [`Git`](https://git-scm.com/) - version control. +Recommended [`React developer tools`](https://react.dev/learn/react-developer-tools). + +To automatically set correct node version: + +1. Install [`nvm`](https://github.com/nvm-sh/nvm) +2. Install `lts/hydrogen` version of node + +```bash + nvm install lts/hydrogen +``` +3. Having that every time you enter the `govtool/frontend` package [`nvm`](https://github.com/nvm-sh/nvm) automatically sets the correct version of node. ## To Develop @@ -118,47 +94,53 @@ Recommendetd [`React developer tools`](https://react.dev/learn/react-developer-t npm install ``` +or (recommended) + +```bash +yarn install +``` + 2. Launch Server ```bash npm run dev ``` -### Using nix +or (recommended) + +```bash +yarn dev +``` + +#### Using Nix and Direnv 1. Get [Nix](https://nixos.org/download). -2. Enter `govtool/frontend` directory: +2. Get [direnv](https://direnv.net/). + +3. Fill .envrc based on envrc.example file in project root. + +4. Enter `govtool/frontend` directory: + ```sh cd govtool/frontend ``` -3. Run `nix-shell` -```sh -nix-shell -``` +5. Allow direnv to setup your environment: -4. Run project ```sh -npm run dev +direnv allow ``` -##### Alternative - -You can skip point 3 by using an automatic direnv configuration: +5. Run project -Install [`direnv`](https://direnv.net/): ```sh -nix-env -i direnv +yarn dev ``` -Allow direnv configuration in `govtool/frontend`: -```sh -direnv allow govtool/frontend -``` +## After development -From now on, once you enter the `govtool/frontend` the `nix-shell` with proper -configuration will be propagated. +Check our [Contributing Documentation](../../CONTRIBUTING.md) on how to submit a PR. ### Users @@ -166,11 +148,16 @@ The GovTool application can read and display data from the Cardano chain using R We distinguish two types of users: #### without a connected wallet who can: -1. see the governance actions along with their details and the number of votes + +1. See the governance actions along with their details and the number of votes + #### with connected wallet who can: -1. see the governance actions along with their details and the number of votes. -2. display the wallet status -3. delegate his or her voting power in a form of ADA to dReps, -4. register as DRrep -5. vote for the governance actions of his or her choice (if the user is registered) + +1. See the governance actions along with their details and the number of votes. +2. Display the wallet status. +3. Delegate his or her voting power in a form of ADA to dReps. +4. Register as DRrep or Sole Voter. +5. Vote for the Governance Actions of his or her choice (if the user is registered). +6. Create their own Governance Action. + diff --git a/govtool/frontend/default.nix b/govtool/frontend/default.nix index e96a00e56..29be71953 100644 --- a/govtool/frontend/default.nix +++ b/govtool/frontend/default.nix @@ -5,6 +5,18 @@ let src = ./.; packageJSON = ./package.json; yarnLock = ./yarn.lock; + nodejs = pkgs.nodejs_18; }; in -project +project.overrideAttrs (attrs: { + shellHook = '' + function warn() { tput setaf $2; echo "$1"; tput sgr0; } + + tput bold + warn "Welcome to GovTool!" 4 + warn "This is a frontend development shell." 4 + warn "Read the ${./README.md} to get more info about this module." 8 + rm -rf ./node_modules + ln -s ${project.out}/libexec/voltaire-voting-app/node_modules ./node_modules + ''; +}) diff --git a/govtool/frontend/index.html b/govtool/frontend/index.html index ac87ebc7a..9fa7c8404 100644 --- a/govtool/frontend/index.html +++ b/govtool/frontend/index.html @@ -18,6 +18,9 @@ overscroll-behavior-y: none; padding: 0; } + #root { + white-space: pre-line; + } diff --git a/govtool/frontend/package-lock.json b/govtool/frontend/package-lock.json index a48263a14..b1ab27abd 100644 --- a/govtool/frontend/package-lock.json +++ b/govtool/frontend/package-lock.json @@ -7,13 +7,16 @@ "": { "name": "voltaire-voting-app", "version": "0.0.0", + "hasInstallScript": true, "dependencies": { "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", + "@emurgo/cardano-serialization-lib-asmjs": "12.0.0-alpha.19", "@hookform/resolvers": "^3.3.1", "@mui/icons-material": "^5.14.3", "@mui/material": "^5.14.4", "@sentry/react": "^7.77.0", + "@types/jsonld": "^1.5.13", "@types/react": "^18.2.12", "@types/react-gtm-module": "^2.0.2", "axios": "^1.4.0", @@ -25,6 +28,7 @@ "i18next": "^23.7.19", "jsonld": "^8.3.2", "keen-slider": "^6.8.5", + "patch-package": "^8.0.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-gtm-module": "^2.0.11", @@ -37,7 +41,6 @@ "yup": "^1.3.2" }, "devDependencies": { - "@emurgo/cardano-serialization-lib-asmjs": "12.0.0-alpha.16", "@storybook/addon-coverage": "^1.0.0", "@storybook/addon-essentials": "^7.4.5", "@storybook/addon-interactions": "^7.6.1", @@ -55,12 +58,17 @@ "@types/jsonld": "^1.5.13", "@types/node": "^20.4.8", "@types/react-dom": "^18.0.11", - "@typescript-eslint/eslint-plugin": "^5.59.0", - "@typescript-eslint/parser": "^5.59.0", + "@typescript-eslint/eslint-plugin": "^7.3.1", + "@typescript-eslint/parser": "^7.3.1", "@vitejs/plugin-react": "^4.0.0", "@vitest/ui": "^1.1.0", - "chromatic": "^10.0.0", + "chromatic": "^11.3.0", "eslint": "^8.38.0", + "eslint-config-airbnb": "^19.0.4", + "eslint-plugin-import": "^2.29.1", + "eslint-plugin-jest": "^27.9.0", + "eslint-plugin-jsx-a11y": "^6.8.0", + "eslint-plugin-react": "^7.34.1", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.3.4", "eslint-plugin-storybook": "^0.6.14", @@ -2577,11 +2585,9 @@ "license": "MIT" }, "node_modules/@emurgo/cardano-serialization-lib-asmjs": { - "version": "12.0.0-alpha.16", - "resolved": "https://registry.npmjs.org/@emurgo/cardano-serialization-lib-asmjs/-/cardano-serialization-lib-asmjs-12.0.0-alpha.16.tgz", - "integrity": "sha512-7AaQMlhA6/N/MWCiUUwtZtQmsHTkMjIGB9UfzN34qextTmk5Dvx++4UORedo/TVCs4lSfcBXZrNZClC7d9f8Fg==", - "dev": true, - "license": "MIT" + "version": "12.0.0-alpha.19", + "resolved": "https://registry.npmjs.org/@emurgo/cardano-serialization-lib-asmjs/-/cardano-serialization-lib-asmjs-12.0.0-alpha.19.tgz", + "integrity": "sha512-fF3e5qPgJeYDDmPVcyZ7nCuqYI6H3JJ5cSjxnKRRKK/FzYxEsHRoxpLCuZ604A4j/4mYNXVlUYeV4KhgecgUBw==" }, "node_modules/@esbuild/darwin-arm64": { "version": "0.19.8", @@ -2679,11 +2685,10 @@ } }, "node_modules/@eslint/js": { - "version": "8.55.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.55.0.tgz", - "integrity": "sha512-qQfo2mxH5yVom1kacMtZZJFVdW+E70mqHMJvVg6WTLo+VBuQJ4TojZlfWBjK0ve5BdEeNAVxOsl/nvNMpJOaJA==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", "dev": true, - "license": "MIT", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } @@ -2768,14 +2773,13 @@ } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.13", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", - "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", "dev": true, - "license": "Apache-2.0", "dependencies": { - "@humanwhocodes/object-schema": "^2.0.1", - "debug": "^4.1.1", + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", "minimatch": "^3.0.5" }, "engines": { @@ -2797,11 +2801,10 @@ } }, "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", - "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", - "dev": true, - "license": "BSD-3-Clause" + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz", + "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==", + "dev": true }, "node_modules/@isaacs/cliui": { "version": "8.0.2", @@ -7234,6 +7237,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, "node_modules/@types/jsonld": { "version": "1.5.13", "resolved": "https://registry.npmjs.org/@types/jsonld/-/jsonld-1.5.13.tgz", @@ -7470,33 +7479,91 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", - "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.3.1.tgz", + "integrity": "sha512-STEDMVQGww5lhCuNXVSQfbfuNII5E08QWkvAw5Qwf+bj2WT+JkG1uc+5/vXA3AOYMDHVOSpL+9rcbEUiHIm2dw==", "dev": true, - "license": "MIT", "dependencies": { - "@eslint-community/regexpp": "^4.4.0", - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/type-utils": "5.62.0", - "@typescript-eslint/utils": "5.62.0", + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "7.3.1", + "@typescript-eslint/type-utils": "7.3.1", + "@typescript-eslint/utils": "7.3.1", + "@typescript-eslint/visitor-keys": "7.3.1", "debug": "^4.3.4", "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "natural-compare-lite": "^1.4.0", - "semver": "^7.3.7", - "tsutils": "^3.21.0" + "ignore": "^5.2.4", + "natural-compare": "^1.4.0", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^5.0.0", - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/scope-manager": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.3.1.tgz", + "integrity": "sha512-fVS6fPxldsKY2nFvyT7IP78UO1/I2huG+AYu5AMjCT9wtl6JFiDnsv4uad4jQ0GTFzcUV5HShVeN96/17bTBag==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.3.1", + "@typescript-eslint/visitor-keys": "7.3.1" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/types": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.3.1.tgz", + "integrity": "sha512-2tUf3uWggBDl4S4183nivWQ2HqceOZh1U4hhu4p1tPiIJoRRXrab7Y+Y0p+dozYwZVvLPRI6r5wKe9kToF9FIw==", + "dev": true, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/typescript-estree": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.3.1.tgz", + "integrity": "sha512-tLpuqM46LVkduWP7JO7yVoWshpJuJzxDOPYIVWUUZbW+4dBpgGeUdl/fQkhuV0A8eGnphYw3pp8d2EnvPOfxmQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.3.1", + "@typescript-eslint/visitor-keys": "7.3.1", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "9.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" }, "peerDependenciesMeta": { "typescript": { @@ -7504,27 +7571,142 @@ } } }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.3.1.tgz", + "integrity": "sha512-jIERm/6bYQ9HkynYlNZvXpzmXWZGhMbrOvq3jJzOSOlKXsVjrrolzWBjDW6/TvT5Q3WqaN4EkmcfdQwi9tDjBQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "7.3.1", + "@typescript-eslint/types": "7.3.1", + "@typescript-eslint/typescript-estree": "7.3.1", + "semver": "^7.5.4" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.3.1.tgz", + "integrity": "sha512-9RMXwQF8knsZvfv9tdi+4D/j7dMG28X/wMJ8Jj6eOHyHWwDW4ngQJcqEczSsqIKKjFiLFr40Mnr7a5ulDD3vmw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.3.1", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@typescript-eslint/parser": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", - "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.3.1.tgz", + "integrity": "sha512-Rq49+pq7viTRCH48XAbTA+wdLRrB/3sRq4Lpk0oGDm0VmnjBrAOVXH/Laalmwsv2VpekiEfVFwJYVk6/e8uvQw==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/typescript-estree": "5.62.0", + "@typescript-eslint/scope-manager": "7.3.1", + "@typescript-eslint/types": "7.3.1", + "@typescript-eslint/typescript-estree": "7.3.1", + "@typescript-eslint/visitor-keys": "7.3.1", "debug": "^4.3.4" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.3.1.tgz", + "integrity": "sha512-fVS6fPxldsKY2nFvyT7IP78UO1/I2huG+AYu5AMjCT9wtl6JFiDnsv4uad4jQ0GTFzcUV5HShVeN96/17bTBag==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.3.1", + "@typescript-eslint/visitor-keys": "7.3.1" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.3.1.tgz", + "integrity": "sha512-2tUf3uWggBDl4S4183nivWQ2HqceOZh1U4hhu4p1tPiIJoRRXrab7Y+Y0p+dozYwZVvLPRI6r5wKe9kToF9FIw==", + "dev": true, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.3.1.tgz", + "integrity": "sha512-tLpuqM46LVkduWP7JO7yVoWshpJuJzxDOPYIVWUUZbW+4dBpgGeUdl/fQkhuV0A8eGnphYw3pp8d2EnvPOfxmQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.3.1", + "@typescript-eslint/visitor-keys": "7.3.1", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "9.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" }, "peerDependenciesMeta": { "typescript": { @@ -7532,6 +7714,38 @@ } } }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.3.1.tgz", + "integrity": "sha512-9RMXwQF8knsZvfv9tdi+4D/j7dMG28X/wMJ8Jj6eOHyHWwDW4ngQJcqEczSsqIKKjFiLFr40Mnr7a5ulDD3vmw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.3.1", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@typescript-eslint/scope-manager": { "version": "5.62.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", @@ -7551,26 +7765,83 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", - "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.3.1.tgz", + "integrity": "sha512-iFhaysxFsMDQlzJn+vr3OrxN8NmdQkHks4WaqD4QBnt5hsq234wcYdyQ9uquzJJIDAj5W4wQne3yEsYA6OmXGw==", "dev": true, - "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "5.62.0", - "@typescript-eslint/utils": "5.62.0", + "@typescript-eslint/typescript-estree": "7.3.1", + "@typescript-eslint/utils": "7.3.1", "debug": "^4.3.4", - "tsutils": "^3.21.0" + "ts-api-utils": "^1.0.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "*" + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/scope-manager": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.3.1.tgz", + "integrity": "sha512-fVS6fPxldsKY2nFvyT7IP78UO1/I2huG+AYu5AMjCT9wtl6JFiDnsv4uad4jQ0GTFzcUV5HShVeN96/17bTBag==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.3.1", + "@typescript-eslint/visitor-keys": "7.3.1" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.3.1.tgz", + "integrity": "sha512-2tUf3uWggBDl4S4183nivWQ2HqceOZh1U4hhu4p1tPiIJoRRXrab7Y+Y0p+dozYwZVvLPRI6r5wKe9kToF9FIw==", + "dev": true, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.3.1.tgz", + "integrity": "sha512-tLpuqM46LVkduWP7JO7yVoWshpJuJzxDOPYIVWUUZbW+4dBpgGeUdl/fQkhuV0A8eGnphYw3pp8d2EnvPOfxmQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.3.1", + "@typescript-eslint/visitor-keys": "7.3.1", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "9.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" }, "peerDependenciesMeta": { "typescript": { @@ -7578,6 +7849,63 @@ } } }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/utils": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.3.1.tgz", + "integrity": "sha512-jIERm/6bYQ9HkynYlNZvXpzmXWZGhMbrOvq3jJzOSOlKXsVjrrolzWBjDW6/TvT5Q3WqaN4EkmcfdQwi9tDjBQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "7.3.1", + "@typescript-eslint/types": "7.3.1", + "@typescript-eslint/typescript-estree": "7.3.1", + "semver": "^7.5.4" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.3.1.tgz", + "integrity": "sha512-9RMXwQF8knsZvfv9tdi+4D/j7dMG28X/wMJ8Jj6eOHyHWwDW4ngQJcqEczSsqIKKjFiLFr40Mnr7a5ulDD3vmw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.3.1", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@typescript-eslint/types": { "version": "5.62.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", @@ -7898,6 +8226,11 @@ "dev": true, "license": "0BSD" }, + "node_modules/@yarnpkg/lockfile": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", + "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==" + }, "node_modules/abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", @@ -8063,7 +8396,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -8079,7 +8411,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -8160,14 +8491,16 @@ } }, "node_modules/array-buffer-byte-length": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", - "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", "dev": true, - "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "is-array-buffer": "^3.0.1" + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -8180,6 +8513,25 @@ "dev": true, "license": "MIT" }, + "node_modules/array-includes": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.7.tgz", + "integrity": "sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", @@ -8190,6 +8542,129 @@ "node": ">=8" } }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "dependencies": { + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz", + "integrity": "sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==", + "dev": true, + "dependencies": { + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", + "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", + "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.toreversed": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/array.prototype.toreversed/-/array.prototype.toreversed-1.1.2.tgz", + "integrity": "sha512-wwDCoT4Ck4Cz7sLtgUmzR5UV3YF5mFHUlbChCzZBQZ+0m2cl/DH3tKgvphv1nKgFsJ48oCSg6p91q2Vm0I/ZMA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.3.tgz", + "integrity": "sha512-/DdH4TiTmOKzyQbp/eadcCVexiCb36xJg7HshYOYJnNZFDj33GEv0P7GxsynpShhq4OLYJzbGcBDkLsDt7MnNg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.1.0", + "es-shim-unscopables": "^1.0.2" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", + "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", + "dev": true, + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/assert": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/assert/-/assert-2.1.0.tgz", @@ -8227,6 +8702,12 @@ "node": ">=4" } }, + "node_modules/ast-types-flow": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", + "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", + "dev": true + }, "node_modules/async": { "version": "3.2.5", "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", @@ -8247,12 +8728,22 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "license": "MIT" }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/available-typed-arrays": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", "dev": true, - "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, "engines": { "node": ">= 0.4" }, @@ -8260,6 +8751,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/axe-core": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.7.0.tgz", + "integrity": "sha512-M0JtH+hlOL5pLQwHOLNYZaXuhqmvS8oExsqB1SBYgA4Dk7u/xx+YdGHXaK5pyUfed5mYXdlYiphWq3G8cRi5JQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/axios": { "version": "1.6.2", "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz", @@ -8271,6 +8771,15 @@ "proxy-from-env": "^1.1.0" } }, + "node_modules/axobject-query": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz", + "integrity": "sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg==", + "dev": true, + "dependencies": { + "dequal": "^2.0.3" + } + }, "node_modules/babel-core": { "version": "7.0.0-bridge.0", "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz", @@ -8724,7 +9233,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, "license": "MIT", "dependencies": { "fill-range": "^7.0.1" @@ -8899,15 +9407,18 @@ } }, "node_modules/call-bind": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", - "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", - "dev": true, - "license": "MIT", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.1", - "set-function-length": "^1.1.1" + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -8991,7 +9502,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", @@ -9063,19 +9573,18 @@ "license": "ISC" }, "node_modules/chromatic": { - "version": "10.7.1", - "resolved": "https://registry.npmjs.org/chromatic/-/chromatic-10.7.1.tgz", - "integrity": "sha512-ERxfVxTm8gnbv3bdWeNyUVQ4msbtYc/dZIpt+3TVxqEq4tMlzVCnTFl1rDfJ7Jj1enGFoxvZ+Q2xon7Jfi+cZw==", + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/chromatic/-/chromatic-11.3.0.tgz", + "integrity": "sha512-q1ZtJDJrjLGnz60ivpC16gmd7KFzcaA4eTb7gcytCqbaKqlHhCFr1xQmcUDsm14CK7JsqdkFU6S+JQdOd2ZNJg==", "dev": true, - "license": "MIT", "bin": { "chroma": "dist/bin.js", "chromatic": "dist/bin.js", "chromatic-cli": "dist/bin.js" }, "peerDependencies": { - "@chromatic-com/cypress": "^0.5.2 || ^1.0.0", - "@chromatic-com/playwright": "^0.5.2 || ^1.0.0" + "@chromatic-com/cypress": "^0.*.* || ^1.0.0", + "@chromatic-com/playwright": "^0.*.* || ^1.0.0" }, "peerDependenciesMeta": { "@chromatic-com/cypress": { @@ -9090,7 +9599,6 @@ "version": "3.9.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", - "dev": true, "funding": [ { "type": "github", @@ -9399,6 +9907,12 @@ "typedarray": "^0.0.6" } }, + "node_modules/confusing-browser-globals": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", + "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", + "dev": true + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -9530,7 +10044,6 @@ "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -9591,6 +10104,12 @@ "node": ">=0.8" } }, + "node_modules/damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", + "dev": true + }, "node_modules/data-uri-to-buffer": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", @@ -9613,6 +10132,57 @@ "node": ">=18" } }, + "node_modules/data-view-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", + "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", + "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", + "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/date-fns": { "version": "2.30.0", "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", @@ -9789,18 +10359,19 @@ } }, "node_modules/define-data-property": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", - "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", - "dev": true, - "license": "MIT", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "dependencies": { - "get-intrinsic": "^1.2.1", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/define-lazy-prop": { @@ -10260,6 +10831,85 @@ "is-arrayish": "^0.2.1" } }, + "node_modules/es-abstract": { + "version": "1.23.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.2.tgz", + "integrity": "sha512-60s3Xv2T2p1ICykc7c+DNDPLDMm9t4QxCOUU0K9JxiLjM3C1zB9YVdN7tjxrFd4+AkZ8CdX1ovUga4P2+1e+/w==", + "dev": true, + "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.6", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "hasown": "^2.0.2", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.1", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.3", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.13", + "is-weakref": "^1.0.2", + "object-inspect": "^1.13.1", + "object-keys": "^1.1.1", + "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.7", + "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.5", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.15" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-get-iterator": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", @@ -10281,6 +10931,31 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-iterator-helpers": { + "version": "1.0.18", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.18.tgz", + "integrity": "sha512-scxAJaewsahbqTYrGKJihhViaM6DDZDDoucfvzNbK0pOren1g/daDQ3IAhzn+1G14rBG7w+i5N+qul60++zlKA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.0", + "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" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-module-lexer": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", @@ -10288,6 +10963,58 @@ "dev": true, "license": "MIT" }, + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", + "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", + "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/es6-error": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", @@ -10391,17 +11118,16 @@ } }, "node_modules/eslint": { - "version": "8.55.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.55.0.tgz", - "integrity": "sha512-iyUUAM0PCKj5QpwGfmCAG9XXbZCWsqP/eWAWrG/W0umvjuLRBECwSFdt+rCntju0xEH7teIABPwXpahftIaTdA==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", "dev": true, - "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.55.0", - "@humanwhocodes/config-array": "^0.11.13", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "@ungap/structured-clone": "^1.2.0", @@ -10446,6 +11172,255 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint-config-airbnb": { + "version": "19.0.4", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb/-/eslint-config-airbnb-19.0.4.tgz", + "integrity": "sha512-T75QYQVQX57jiNgpF9r1KegMICE94VYwoFQyMGhrvc+lB8YF2E/M/PYDaQe1AJcWaEgqLE+ErXV1Og/+6Vyzew==", + "dev": true, + "dependencies": { + "eslint-config-airbnb-base": "^15.0.0", + "object.assign": "^4.1.2", + "object.entries": "^1.1.5" + }, + "engines": { + "node": "^10.12.0 || ^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^7.32.0 || ^8.2.0", + "eslint-plugin-import": "^2.25.3", + "eslint-plugin-jsx-a11y": "^6.5.1", + "eslint-plugin-react": "^7.28.0", + "eslint-plugin-react-hooks": "^4.3.0" + } + }, + "node_modules/eslint-config-airbnb-base": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz", + "integrity": "sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==", + "dev": true, + "dependencies": { + "confusing-browser-globals": "^1.0.10", + "object.assign": "^4.1.2", + "object.entries": "^1.1.5", + "semver": "^6.3.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "peerDependencies": { + "eslint": "^7.32.0 || ^8.2.0", + "eslint-plugin-import": "^2.25.2" + } + }, + "node_modules/eslint-config-airbnb-base/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.1.tgz", + "integrity": "sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==", + "dev": true, + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", + "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", + "dev": true, + "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.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.fromentries": "^2.0.7", + "object.groupby": "^1.0.1", + "object.values": "^1.1.7", + "semver": "^6.3.1", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-jest": { + "version": "27.9.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-27.9.0.tgz", + "integrity": "sha512-QIT7FH7fNmd9n4se7FFKHbsLKGQiw885Ds6Y/sxKgCZ6natwCsXdgPOADnYVxN2QrRweF0FZWbJ6S7Rsn7llug==", + "dev": true, + "dependencies": { + "@typescript-eslint/utils": "^5.10.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^5.0.0 || ^6.0.0 || ^7.0.0", + "eslint": "^7.0.0 || ^8.0.0", + "jest": "*" + }, + "peerDependenciesMeta": { + "@typescript-eslint/eslint-plugin": { + "optional": true + }, + "jest": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-jsx-a11y": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.8.0.tgz", + "integrity": "sha512-Hdh937BS3KdwwbBaKd5+PLCOmYY6U4f2h9Z2ktwtNKvIdIEu137rjYbcb9ApSbVJfWxANNuiKTD/9tOKjK9qOA==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.23.2", + "aria-query": "^5.3.0", + "array-includes": "^3.1.7", + "array.prototype.flatmap": "^1.3.2", + "ast-types-flow": "^0.0.8", + "axe-core": "=4.7.0", + "axobject-query": "^3.2.1", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", + "es-iterator-helpers": "^1.0.15", + "hasown": "^2.0.0", + "jsx-ast-utils": "^3.3.5", + "language-tags": "^1.0.9", + "minimatch": "^3.1.2", + "object.entries": "^1.1.7", + "object.fromentries": "^2.0.7" + }, + "engines": { + "node": ">=4.0" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" + } + }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/eslint-plugin-react": { + "version": "7.34.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.34.1.tgz", + "integrity": "sha512-N97CxlouPT1AHt8Jn0mhhN2RrADlUAsk1/atcT2KyA/l9Q/E6ll7OIGwNumFmWfZ9skV3XXccYS19h80rHtgkw==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.7", + "array.prototype.findlast": "^1.2.4", + "array.prototype.flatmap": "^1.3.2", + "array.prototype.toreversed": "^1.1.2", + "array.prototype.tosorted": "^1.1.3", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.0.17", + "estraverse": "^5.3.0", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.7", + "object.fromentries": "^2.0.7", + "object.hasown": "^1.1.3", + "object.values": "^1.1.7", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.10" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" + } + }, "node_modules/eslint-plugin-react-hooks": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", @@ -10469,6 +11444,44 @@ "eslint": ">=7" } }, + "node_modules/eslint-plugin-react/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-plugin-react/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/eslint-plugin-storybook": { "version": "0.6.15", "resolved": "https://registry.npmjs.org/eslint-plugin-storybook/-/eslint-plugin-storybook-0.6.15.tgz", @@ -11128,7 +12141,6 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" @@ -11263,6 +12275,14 @@ "node": ">=8" } }, + "node_modules/find-yarn-workspace-root": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz", + "integrity": "sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==", + "dependencies": { + "micromatch": "^4.0.2" + } + }, "node_modules/flat-cache": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", @@ -11479,6 +12499,24 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/function.prototype.name": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", + "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/functions-have-names": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", @@ -11520,17 +12558,19 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", - "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", - "dev": true, - "license": "MIT", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "dependencies": { + "es-errors": "^1.3.0", "function-bind": "^1.1.2", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", "hasown": "^2.0.0" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -11591,6 +12631,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/get-symbol-description": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", + "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/giget": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/giget/-/giget-1.1.3.tgz", @@ -11730,6 +12787,21 @@ "node": ">=4" } }, + "node_modules/globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/globby": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", @@ -11755,7 +12827,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dev": true, "license": "MIT", "dependencies": { "get-intrinsic": "^1.1.3" @@ -11768,7 +12839,6 @@ "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, "license": "ISC" }, "node_modules/graphemer": { @@ -11832,31 +12902,26 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/has-property-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", - "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", - "dev": true, - "license": "MIT", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "dependencies": { - "get-intrinsic": "^1.2.2" + "es-define-property": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", - "dev": true, - "license": "MIT", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", "engines": { "node": ">= 0.4" }, @@ -11868,7 +12933,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -11878,13 +12942,12 @@ } }, "node_modules/has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "dev": true, - "license": "MIT", "dependencies": { - "has-symbols": "^1.0.2" + "has-symbols": "^1.0.3" }, "engines": { "node": ">= 0.4" @@ -11921,10 +12984,9 @@ } }, "node_modules/hasown": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", - "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", - "license": "MIT", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "dependencies": { "function-bind": "^1.1.2" }, @@ -12299,13 +13361,12 @@ "license": "ISC" }, "node_modules/internal-slot": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.6.tgz", - "integrity": "sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", "dev": true, - "license": "MIT", "dependencies": { - "get-intrinsic": "^1.2.2", + "es-errors": "^1.3.0", "hasown": "^2.0.0", "side-channel": "^1.0.4" }, @@ -12368,15 +13429,16 @@ } }, "node_modules/is-array-buffer": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", - "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", + "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", "dev": true, - "license": "MIT", "dependencies": { "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.0", - "is-typed-array": "^1.1.10" + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -12388,6 +13450,21 @@ "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "license": "MIT" }, + "node_modules/is-async-function": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz", + "integrity": "sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-bigint": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", @@ -12456,6 +13533,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-data-view": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", + "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", + "dev": true, + "dependencies": { + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-date-object": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", @@ -12483,7 +13575,6 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", - "dev": true, "license": "MIT", "bin": { "is-docker": "cli.js" @@ -12505,6 +13596,18 @@ "node": ">=0.10.0" } }, + "node_modules/is-finalizationregistry": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz", + "integrity": "sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -12601,11 +13704,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.12.0" @@ -12692,13 +13806,15 @@ } }, "node_modules/is-shared-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", - "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", + "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", "dev": true, - "license": "MIT", "dependencies": { - "call-bind": "^1.0.2" + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -12750,13 +13866,12 @@ } }, "node_modules/is-typed-array": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", - "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", "dev": true, - "license": "MIT", "dependencies": { - "which-typed-array": "^1.1.11" + "which-typed-array": "^1.1.14" }, "engines": { "node": ">= 0.4" @@ -12795,6 +13910,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-weakset": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", @@ -12823,7 +13950,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "dev": true, "license": "MIT", "dependencies": { "is-docker": "^2.0.0" @@ -12836,14 +13962,12 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true, "license": "MIT" }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, "license": "ISC" }, "node_modules/isobject": { @@ -12983,6 +14107,19 @@ "node": ">=8" } }, + "node_modules/iterator.prototype": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.2.tgz", + "integrity": "sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==", + "dev": true, + "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" + } + }, "node_modules/jackspeak": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", @@ -13994,6 +15131,23 @@ "dev": true, "license": "MIT" }, + "node_modules/json-stable-stringify": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.1.1.tgz", + "integrity": "sha512-SU/971Kt5qVQfJpyDveVhQ/vya+5hvrjClFOcr8c0Fq5aODJjMwutrOfCU+eCnVD5gpx1Q3fEqkyom77zH1iIg==", + "dependencies": { + "call-bind": "^1.0.5", + "isarray": "^2.0.5", + "jsonify": "^0.0.1", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", @@ -14025,7 +15179,6 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, "license": "MIT", "dependencies": { "universalify": "^2.0.0" @@ -14034,6 +15187,14 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/jsonify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.1.tgz", + "integrity": "sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/jsonld": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/jsonld/-/jsonld-8.3.2.tgz", @@ -14048,6 +15209,21 @@ "node": ">=14" } }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, "node_modules/keen-slider": { "version": "6.8.6", "resolved": "https://registry.npmjs.org/keen-slider/-/keen-slider-6.8.6.tgz", @@ -14074,6 +15250,14 @@ "node": ">=0.10.0" } }, + "node_modules/klaw-sync": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz", + "integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==", + "dependencies": { + "graceful-fs": "^4.1.11" + } + }, "node_modules/kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", @@ -14136,6 +15320,24 @@ "url": "https://opencollective.com/node-fetch" } }, + "node_modules/language-subtag-registry": { + "version": "0.3.22", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz", + "integrity": "sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==", + "dev": true + }, + "node_modules/language-tags": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", + "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", + "dev": true, + "dependencies": { + "language-subtag-registry": "^0.3.20" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/lazy-universal-dotenv": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/lazy-universal-dotenv/-/lazy-universal-dotenv-4.0.0.tgz", @@ -14493,7 +15695,6 @@ "version": "4.0.5", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, "license": "MIT", "dependencies": { "braces": "^3.0.2", @@ -14589,7 +15790,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -14730,13 +15930,6 @@ "dev": true, "license": "MIT" }, - "node_modules/natural-compare-lite": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", - "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", - "dev": true, - "license": "MIT" - }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -15007,7 +16200,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -15032,6 +16224,82 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/object.entries": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.8.tgz", + "integrity": "sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.hasown": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.3.tgz", + "integrity": "sha512-fFI4VcYpRHvSLXxP7yiZOMAd331cPfd2p7PFDVbgUsYOfCT3tICVqXWngbjr4m49OvsBwUBQ6O2uQoJvy3RexA==", + "dev": true, + "dependencies": { + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.values": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", + "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/oblivious-set": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/oblivious-set/-/oblivious-set-1.0.0.tgz", @@ -15156,6 +16424,14 @@ "node": ">=0.10.0" } }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -15287,27 +16563,115 @@ "node": ">=0.10.0" } }, - "node_modules/parse5": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", - "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", - "dev": true, - "license": "MIT", + "node_modules/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/patch-package": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/patch-package/-/patch-package-8.0.0.tgz", + "integrity": "sha512-da8BVIhzjtgScwDJ2TtKsfT5JFWz1hYoBl9rUQ1f38MC2HwnEIkK8VN3dKMKcP7P7bvvgzNDbfNHtx3MsQb5vA==", + "dependencies": { + "@yarnpkg/lockfile": "^1.1.0", + "chalk": "^4.1.2", + "ci-info": "^3.7.0", + "cross-spawn": "^7.0.3", + "find-yarn-workspace-root": "^2.0.0", + "fs-extra": "^9.0.0", + "json-stable-stringify": "^1.0.2", + "klaw-sync": "^6.0.0", + "minimist": "^1.2.6", + "open": "^7.4.2", + "rimraf": "^2.6.3", + "semver": "^7.5.3", + "slash": "^2.0.0", + "tmp": "^0.0.33", + "yaml": "^2.2.2" + }, + "bin": { + "patch-package": "index.js" + }, + "engines": { + "node": ">=14", + "npm": ">5" + } + }, + "node_modules/patch-package/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/patch-package/node_modules/open": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", + "dependencies": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/patch-package/node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", "dependencies": { - "entities": "^4.4.0" + "glob": "^7.1.3" }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" + "bin": { + "rimraf": "bin.js" } }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "dev": true, - "license": "MIT", + "node_modules/patch-package/node_modules/slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", "engines": { - "node": ">= 0.8" + "node": ">=6" + } + }, + "node_modules/patch-package/node_modules/yaml": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.1.tgz", + "integrity": "sha512-pIXzoImaqmfOrL7teGUBt/T7ZDnyeGBWyXQBvOVhLkWLN37GXv8NMLK406UY6dS51JfcQHsmcW5cJ441bHg6Lg==", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" } }, "node_modules/path-exists": { @@ -15333,7 +16697,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -15445,7 +16808,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, "license": "MIT", "engines": { "node": ">=8.6" @@ -15558,6 +16920,15 @@ "node": ">=10" } }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/postcss": { "version": "8.4.32", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz", @@ -16477,6 +17848,27 @@ "node": ">=8" } }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz", + "integrity": "sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==", + "dev": true, + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -16514,15 +17906,15 @@ } }, "node_modules/regexp.prototype.flags": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz", - "integrity": "sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", + "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", "dev": true, - "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "set-function-name": "^2.0.0" + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" }, "engines": { "node": ">= 0.4" @@ -16820,6 +18212,24 @@ "tslib": "^2.1.0" } }, + "node_modules/safe-array-concat": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", + "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -16827,6 +18237,23 @@ "dev": true, "license": "MIT" }, + "node_modules/safe-regex-test": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", + "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-regex": "^1.1.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -16879,7 +18306,6 @@ "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, "license": "ISC", "dependencies": { "lru-cache": "^6.0.0" @@ -16977,31 +18403,31 @@ "license": "ISC" }, "node_modules/set-function-length": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", - "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", - "dev": true, - "license": "MIT", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", "dependencies": { - "define-data-property": "^1.1.1", - "get-intrinsic": "^1.2.1", + "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.0" + "has-property-descriptors": "^1.0.2" }, "engines": { "node": ">= 0.4" } }, "node_modules/set-function-name": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.1.tgz", - "integrity": "sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", "dev": true, - "license": "MIT", "dependencies": { - "define-data-property": "^1.0.1", + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", - "has-property-descriptors": "^1.0.0" + "has-property-descriptors": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -17036,7 +18462,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -17049,22 +18474,24 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", "dev": true, - "license": "MIT", "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" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -17459,6 +18886,78 @@ "node": ">=8" } }, + "node_modules/string.prototype.matchall": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz", + "integrity": "sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg==", + "dev": true, + "dependencies": { + "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", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.7", + "regexp.prototype.flags": "^1.5.2", + "set-function-name": "^2.0.2", + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", + "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", + "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz", + "integrity": "sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -17567,7 +19066,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -17868,6 +19366,17 @@ "node": ">=14.0.0" } }, + "node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -17888,7 +19397,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "license": "MIT", "dependencies": { "is-number": "^7.0.0" @@ -17979,6 +19487,18 @@ "tree-kill": "cli.js" } }, + "node_modules/ts-api-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", + "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "dev": true, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, "node_modules/ts-dedent": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz", @@ -17989,6 +19509,39 @@ "node": ">=6.10" } }, + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/tsconfig-paths/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/tslib": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", @@ -18075,6 +19628,79 @@ "node": ">= 0.6" } }, + "node_modules/typed-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", + "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", + "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", + "dev": true, + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", + "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", + "dev": true, + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.5.tgz", + "integrity": "sha512-yMi0PlwuznKHxKmcpoOdeLwxBoVPkqZxd7q2FgMkmD3bNwvF5VW0+UlUQ1k1vmktTu4Yu13Q0RIxEP8+B+wloA==", + "dev": true, + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", @@ -18127,6 +19753,21 @@ "node": ">=0.8.0" } }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/undici": { "version": "5.28.3", "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.3.tgz", @@ -18248,7 +19889,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 10.0.0" @@ -19454,7 +21094,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -19483,6 +21122,32 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/which-builtin-type": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.1.3.tgz", + "integrity": "sha512-YmjsSMDBYsM1CaFiayOVT06+KJeXf0o5M/CAd4o1lTadFAtacTUM49zoYxr/oroopFDfhvN6iEcBxUyc3gvKmw==", + "dev": true, + "dependencies": { + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/which-collection": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", @@ -19507,17 +21172,16 @@ "license": "ISC" }, "node_modules/which-typed-array": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.13.tgz", - "integrity": "sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", + "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", "dev": true, - "license": "MIT", "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.4", + "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" + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" diff --git a/govtool/frontend/package.json b/govtool/frontend/package.json index 8ce160dd4..b27e98257 100644 --- a/govtool/frontend/package.json +++ b/govtool/frontend/package.json @@ -4,20 +4,24 @@ "version": "0.0.0", "type": "module", "scripts": { - "dev": "vite", "build": "vite build", + "build-storybook": "storybook build", + "chromatic": "chromatic", + "dev": "vite", + "format": "prettier --write src", "lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "postinstall": "patch-package", "preview": "vite preview", "storybook": "storybook dev -p 6006", - "build-storybook": "storybook build", - "test-storybook": "test-storybook", - "chromatic": "chromatic", "test": "vitest", - "test:watch": "vitest watch" + "test-storybook": "test-storybook", + "test:watch": "vitest watch", + "tsc": "npx tsc --noEmit --skipLibCheck" }, "dependencies": { "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", + "@emurgo/cardano-serialization-lib-asmjs": "12.0.0-alpha.19", "@hookform/resolvers": "^3.3.1", "@mui/icons-material": "^5.14.3", "@mui/material": "^5.14.4", @@ -34,6 +38,7 @@ "i18next": "^23.7.19", "jsonld": "^8.3.2", "keen-slider": "^6.8.5", + "patch-package": "^8.0.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-gtm-module": "^2.0.11", @@ -46,7 +51,6 @@ "yup": "^1.3.2" }, "devDependencies": { - "@emurgo/cardano-serialization-lib-asmjs": "12.0.0-alpha.16", "@storybook/addon-coverage": "^1.0.0", "@storybook/addon-essentials": "^7.4.5", "@storybook/addon-interactions": "^7.6.1", @@ -64,12 +68,17 @@ "@types/jsonld": "^1.5.13", "@types/node": "^20.4.8", "@types/react-dom": "^18.0.11", - "@typescript-eslint/eslint-plugin": "^5.59.0", - "@typescript-eslint/parser": "^5.59.0", + "@typescript-eslint/eslint-plugin": "^7.3.1", + "@typescript-eslint/parser": "^7.3.1", "@vitejs/plugin-react": "^4.0.0", "@vitest/ui": "^1.1.0", - "chromatic": "^10.0.0", + "chromatic": "^11.3.0", "eslint": "^8.38.0", + "eslint-config-airbnb": "^19.0.4", + "eslint-plugin-import": "^2.29.1", + "eslint-plugin-jest": "^27.9.0", + "eslint-plugin-jsx-a11y": "^6.8.0", + "eslint-plugin-react": "^7.34.1", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.3.4", "eslint-plugin-storybook": "^0.6.14", @@ -82,14 +91,6 @@ "optionalDependencies": { "@rollup/rollup-linux-arm64-musl": "4.12.0" }, - "eslintConfig": { - "extends": [ - "plugin:storybook/recommended" - ] - }, - "resolutions": { - "jackspeak": "2.1.1" - }, "readme": "ERROR: No README data found!", "_id": "voltaire-voting-app@0.0.0" } diff --git a/govtool/frontend/patches/@emurgo+cardano-serialization-lib-asmjs+12.0.0-alpha.19.patch b/govtool/frontend/patches/@emurgo+cardano-serialization-lib-asmjs+12.0.0-alpha.19.patch new file mode 100644 index 000000000..bf0c63b21 --- /dev/null +++ b/govtool/frontend/patches/@emurgo+cardano-serialization-lib-asmjs+12.0.0-alpha.19.patch @@ -0,0 +1,12 @@ +diff --git a/node_modules/@emurgo/cardano-serialization-lib-asmjs/package.json b/node_modules/@emurgo/cardano-serialization-lib-asmjs/package.json +index 7140a36..999ac42 100644 +--- a/node_modules/@emurgo/cardano-serialization-lib-asmjs/package.json ++++ b/node_modules/@emurgo/cardano-serialization-lib-asmjs/package.json +@@ -18,6 +18,7 @@ + "cardano_serialization_lib.js.flow" + ], + "module": "cardano_serialization_lib.js", ++ "main":"cardano_serialization_lib.js", + "types": "cardano_serialization_lib.d.ts", + "sideEffects": [ + "./cardano_serialization_lib.js", diff --git a/govtool/frontend/public/icons/CopyBlueThin.svg b/govtool/frontend/public/icons/CopyBlueThin.svg new file mode 100644 index 000000000..2050c9abe --- /dev/null +++ b/govtool/frontend/public/icons/CopyBlueThin.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/govtool/frontend/public/icons/Download.svg b/govtool/frontend/public/icons/Download.svg index c39977182..d283aaaef 100644 --- a/govtool/frontend/public/icons/Download.svg +++ b/govtool/frontend/public/icons/Download.svg @@ -1,5 +1,5 @@ - - - + + + diff --git a/govtool/frontend/public/icons/Share.svg b/govtool/frontend/public/icons/Share.svg new file mode 100644 index 000000000..a8b9eac2b --- /dev/null +++ b/govtool/frontend/public/icons/Share.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/govtool/frontend/src/App.tsx b/govtool/frontend/src/App.tsx index 3c6ead960..c73ca3bfb 100644 --- a/govtool/frontend/src/App.tsx +++ b/govtool/frontend/src/App.tsx @@ -4,6 +4,7 @@ import { Route, Routes, useNavigate } from "react-router-dom"; import { Modal, ScrollToTop } from "@atoms"; import { PATHS } from "@consts"; import { useCardano, useModal } from "@context"; +import { useWalletConnectionListener } from "@hooks"; import { DashboardCards, DashboardGovernanceActions, @@ -11,33 +12,32 @@ import { } from "@organisms"; import { ChooseStakeKey, + CreateGovernanceAction, Dashboard, - ErrorPage, - Home, - GovernanceActions, + DashboardGovernanceActionsCategory, DelegateTodRep, - RegisterAsdRep, + ErrorPage, GovernanceActionDetails, - UpdatedRepMetadata, + GovernanceActions, GovernanceActionsCategory, - DashboardGovernanceActionsCategory, + Home, + RegisterAsdRep, + RegisterAsSoleVoter, + RetireAsDrep, RetireAsSoleVoter, + EditDRepMetadata, } from "@pages"; +import { SetupInterceptors } from "@services"; import { callAll, getItemFromLocalStorage, WALLET_LS_KEY, removeItemFromLocalStorage, } from "@utils"; -import { SetupInterceptors } from "./services"; -import { useGetVoterInfo, useWalletConnectionListener } from "./hooks"; -import { RegisterAsSoleVoter } from "./pages/RegisterAsSoleVoter"; -import { CreateGovernanceAction } from "./pages/CreateGovernanceAction"; -export default function App() { - const { enable, setVoter, setIsDrepLoading } = useCardano(); +export default () => { + const { enable } = useCardano(); const navigate = useNavigate(); - const { data } = useGetVoterInfo(); const { modal, openModal, modals } = useModal(); useWalletConnectionListener(); @@ -46,14 +46,6 @@ export default function App() { SetupInterceptors(navigate); }, []); - useEffect(() => { - setIsDrepLoading(true); - setVoter(data); - const timer = setTimeout(() => setIsDrepLoading(false), 1000); - - return () => clearTimeout(timer); - }, [data?.isRegisteredAsDRep, data?.isRegisteredAsSoleVoter]); - const checkTheWalletIsActive = useCallback(() => { const hrefCondition = window.location.pathname === PATHS.home || @@ -89,10 +81,7 @@ export default function App() { } /> - } - > + } /> } @@ -122,13 +111,14 @@ export default function App() { /> } /> } /> + } /> } /> } /> } /> - } /> + } /> } /> } /> @@ -138,14 +128,14 @@ export default function App() { handleClose={ !modals[modal.type].preventDismiss ? callAll(modals[modal.type]?.onClose, () => - openModal({ type: "none", state: null }) + openModal({ type: "none", state: null }), ) : undefined } > - {modals[modal.type]?.component ?? <>} + {modals[modal.type].component!} )} ); -} +}; diff --git a/govtool/frontend/src/components/atoms/ActionRadio.tsx b/govtool/frontend/src/components/atoms/ActionRadio.tsx index dbea27895..731d354ee 100644 --- a/govtool/frontend/src/components/atoms/ActionRadio.tsx +++ b/govtool/frontend/src/components/atoms/ActionRadio.tsx @@ -2,8 +2,8 @@ import { FC } from "react"; import { Box, Typography } from "@mui/material"; import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined"; -import { Tooltip } from "."; import { useScreenDimension } from "@hooks"; +import { Tooltip } from "."; import { theme } from "@/theme"; type ActionRadioProps = { @@ -38,10 +38,10 @@ export const ActionRadio: FC = ({ ...props }) => { data-testid={dataTestId} aria-checked={isChecked} p="2px" - maxWidth={"623px"} + maxWidth="623px" border="2px solid" borderColor={isChecked ? "specialCyanBorder" : "white"} - bgcolor={"white"} + bgcolor="white" borderRadius="15px" boxShadow={`1px 2px 11px 0px ${boxShadow1}`} onClick={() => onChange(value)} @@ -50,7 +50,7 @@ export const ActionRadio: FC = ({ ...props }) => { bg-orange {children} bg-blue void) => { - useEffect(() => { - document.addEventListener("mousedown", (e) => { - if (ref.current && !ref.current.contains(e.target)) { - onClick(); - } - }); - - return () => { - document.removeEventListener("mousedown", (e) => { - if (ref.current && !ref.current.contains(e.target)) { - onClick(); - } - }); - }; - }, [ref]); -}; - -interface Props { - children: React.ReactElement; - onClick: () => void; -} - -export const ClickOutside = ({ children, onClick }: Props) => { - const wrapperRef = useRef(null); - useOutsideClick(wrapperRef, onClick); - return
{children}
; -}; diff --git a/govtool/frontend/src/components/atoms/CopyButton.tsx b/govtool/frontend/src/components/atoms/CopyButton.tsx index 069344c94..021d5fcc0 100644 --- a/govtool/frontend/src/components/atoms/CopyButton.tsx +++ b/govtool/frontend/src/components/atoms/CopyButton.tsx @@ -4,11 +4,11 @@ import { ICONS } from "@consts"; import { useSnackbar } from "@context"; import { useTranslation } from "@hooks"; -interface Props { +type Props = { isChecked?: boolean; text: string; - variant?: string; -} + variant?: "blueThin" | "blue"; +}; export const CopyButton = ({ isChecked, text, variant }: Props) => { const { addSuccessAlert } = useSnackbar(); @@ -19,6 +19,10 @@ export const CopyButton = ({ isChecked, text, variant }: Props) => { return ICONS.copyBlueIcon; } + if (variant === "blueThin") { + return ICONS.copyBlueThinIcon; + } + if (isChecked) { return ICONS.copyWhiteIcon; } @@ -28,7 +32,7 @@ export const CopyButton = ({ isChecked, text, variant }: Props) => { return ( copy { navigator.clipboard.writeText(text); diff --git a/govtool/frontend/src/components/atoms/DrawerLink.tsx b/govtool/frontend/src/components/atoms/DrawerLink.tsx index 130a7678d..cb9a77d27 100644 --- a/govtool/frontend/src/components/atoms/DrawerLink.tsx +++ b/govtool/frontend/src/components/atoms/DrawerLink.tsx @@ -26,37 +26,34 @@ export const DrawerLink: FC = ({ ...props }) => { onClick={() => { if (onClick) onClick(); }} - style={({ isActive }) => { - return { - textDecoration: "none", - backgroundColor: isActive ? highlightBlue : "transparent", - padding: "8px 16px", - display: "block", - borderRadius: 100, - }; - }} - children={({ isActive }) => { - return ( - - {activeIcon && icon && ( - icon - )} - - {label} - - - ); - }} - > + style={({ isActive }) => ({ + textDecoration: "none", + backgroundColor: isActive ? highlightBlue : "transparent", + padding: "8px 16px", + display: "block", + borderRadius: 100, + })} + > + {({ isActive }) => ( + + {activeIcon && icon && ( + icon + )} + + {label} + + + )} + ); }; diff --git a/govtool/frontend/src/components/atoms/ExternalModalButton.tsx b/govtool/frontend/src/components/atoms/ExternalModalButton.tsx new file mode 100644 index 000000000..8584c15b0 --- /dev/null +++ b/govtool/frontend/src/components/atoms/ExternalModalButton.tsx @@ -0,0 +1,49 @@ +import { Typography } from "@mui/material"; + +import { Button } from "@atoms"; +import { ICONS } from "@consts"; +import { useModal } from "@context"; + +export const ExternalModalButton = ({ + label, + url, +}: { + label: string; + url: string; +}) => { + const { openModal } = useModal(); + + return ( + + ); +}; diff --git a/govtool/frontend/src/components/atoms/FormErrorMessage.tsx b/govtool/frontend/src/components/atoms/FormErrorMessage.tsx index aae8a90ca..2bd3a37cf 100644 --- a/govtool/frontend/src/components/atoms/FormErrorMessage.tsx +++ b/govtool/frontend/src/components/atoms/FormErrorMessage.tsx @@ -5,19 +5,16 @@ import { FormErrorMessageProps } from "./types"; export const FormErrorMessage = ({ errorMessage, errorStyles, -}: FormErrorMessageProps) => { - return ( - errorMessage && ( - - {errorMessage} - - ) +}: FormErrorMessageProps) => + errorMessage && ( + + {errorMessage} + ); -}; diff --git a/govtool/frontend/src/components/atoms/FormHelpfulText.tsx b/govtool/frontend/src/components/atoms/FormHelpfulText.tsx index 4ceed950a..fc969d48c 100644 --- a/govtool/frontend/src/components/atoms/FormHelpfulText.tsx +++ b/govtool/frontend/src/components/atoms/FormHelpfulText.tsx @@ -5,19 +5,16 @@ import { FormHelpfulTextProps } from "./types"; export const FormHelpfulText = ({ helpfulText, helpfulTextStyle, -}: FormHelpfulTextProps) => { - return ( - helpfulText && ( - - {helpfulText} - - ) +}: FormHelpfulTextProps) => + helpfulText && ( + + {helpfulText} + ); -}; diff --git a/govtool/frontend/src/components/atoms/HighlightedText.tsx b/govtool/frontend/src/components/atoms/HighlightedText.tsx index 274ab1c72..9b18e8dc0 100644 --- a/govtool/frontend/src/components/atoms/HighlightedText.tsx +++ b/govtool/frontend/src/components/atoms/HighlightedText.tsx @@ -22,11 +22,12 @@ export const HighlightedText = ({ <> {parts.map((part, index) => ( { - return ( - - {label.toLocaleUpperCase()} - - ); -}; +export const InfoText = ({ label, sx }: InfoTextProps) => ( + + {label.toLocaleUpperCase()} + +); diff --git a/govtool/frontend/src/components/atoms/Input.tsx b/govtool/frontend/src/components/atoms/Input.tsx index 029b47a88..1901f1e7f 100644 --- a/govtool/frontend/src/components/atoms/Input.tsx +++ b/govtool/frontend/src/components/atoms/Input.tsx @@ -32,7 +32,7 @@ export const Input = forwardRef( blur: handleBlur, ...inputRef.current, } as unknown as HTMLInputElement), - [handleBlur, handleFocus] + [handleBlur, handleFocus], ); return ( @@ -59,5 +59,5 @@ export const Input = forwardRef( {...rest} /> ); - } + }, ); diff --git a/govtool/frontend/src/components/atoms/Link.tsx b/govtool/frontend/src/components/atoms/Link.tsx index 3717cfe68..78fc8bccd 100644 --- a/govtool/frontend/src/components/atoms/Link.tsx +++ b/govtool/frontend/src/components/atoms/Link.tsx @@ -39,19 +39,18 @@ export const Link: FC = ({ ...props }) => { if (!isConnectWallet) disconnectWallet(); if (onClick) onClick(); }} - children={({ isActive }) => { - return ( - - {label} - - ); - }} - > + > + {({ isActive }) => ( + + {label} + + )} + ); }; diff --git a/govtool/frontend/src/components/atoms/Radio.tsx b/govtool/frontend/src/components/atoms/Radio.tsx index 87c64ea05..d997c1b58 100644 --- a/govtool/frontend/src/components/atoms/Radio.tsx +++ b/govtool/frontend/src/components/atoms/Radio.tsx @@ -1,5 +1,9 @@ -import { Box, Typography } from "@mui/material"; +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { Box } from "@mui/material"; + +import { Typography } from "@atoms"; import { UseFormRegister, UseFormSetValue } from "react-hook-form"; +import { theme } from "@/theme"; type RadioProps = { isChecked: boolean; @@ -9,11 +13,20 @@ type RadioProps = { setValue: UseFormSetValue; register: UseFormRegister; dataTestId?: string; + disabled?: boolean; }; export const Radio = ({ ...props }: RadioProps) => { - const { isChecked, name, setValue, title, value, dataTestId, register } = - props; + const { + isChecked, + name, + setValue, + title, + value, + dataTestId, + register, + disabled, + } = props; const handleClick = () => { setValue(name, value); @@ -22,13 +35,23 @@ export const Radio = ({ ...props }: RadioProps) => { return ( { + if (!disabled) handleClick(); + }} + borderRadius={isChecked ? "15px" : "12px"} + p={isChecked ? "2px" : 0} + border={isChecked ? 2 : 0} + borderColor={isChecked ? "specialCyanBorder" : undefined} + sx={[ + { + boxShadow: theme.shadows[1], + + "&:hover": { + color: "blue", + cursor: disabled ? "default" : "pointer", + }, + }, + ]} > { checked={isChecked} /> {title} diff --git a/govtool/frontend/src/components/atoms/ScrollToManage.tsx b/govtool/frontend/src/components/atoms/ScrollToManage.tsx index 1b1ff7e1a..543ca62c2 100644 --- a/govtool/frontend/src/components/atoms/ScrollToManage.tsx +++ b/govtool/frontend/src/components/atoms/ScrollToManage.tsx @@ -1,13 +1,14 @@ -import { PATHS } from "@/consts"; +/* eslint-disable @typescript-eslint/no-explicit-any */ import { useEffect } from "react"; import { useLocation } from "react-router-dom"; +import { PATHS } from "@/consts"; export function debounce( fn: (...params: any) => void, - wait: number + wait: number, ): (...params: any) => void { let timer: any = null; - return function (...params: any) { + return (...params: any) => { clearTimeout(timer); timer = setTimeout(() => { fn(...params); diff --git a/govtool/frontend/src/components/atoms/ScrollToTop.tsx b/govtool/frontend/src/components/atoms/ScrollToTop.tsx index bf31b9e3e..6a001cff8 100644 --- a/govtool/frontend/src/components/atoms/ScrollToTop.tsx +++ b/govtool/frontend/src/components/atoms/ScrollToTop.tsx @@ -1,7 +1,7 @@ import { useEffect } from "react"; import { useLocation } from "react-router-dom"; -export function ScrollToTop() { +export const ScrollToTop = () => { const { pathname } = useLocation(); useEffect(() => { @@ -9,4 +9,4 @@ export function ScrollToTop() { }, [pathname]); return null; -} +}; diff --git a/govtool/frontend/src/components/atoms/Spacer.tsx b/govtool/frontend/src/components/atoms/Spacer.tsx index 0c63ad847..7c68fd471 100644 --- a/govtool/frontend/src/components/atoms/Spacer.tsx +++ b/govtool/frontend/src/components/atoms/Spacer.tsx @@ -1,6 +1,4 @@ import { Box } from "@mui/material"; import { SpacerProps } from "."; -export const Spacer = ({ x, y }: SpacerProps) => { - return ; -}; +export const Spacer = ({ x, y }: SpacerProps) => ; diff --git a/govtool/frontend/src/components/atoms/StakeRadio.tsx b/govtool/frontend/src/components/atoms/StakeRadio.tsx index 4fe7c584e..61dd45b39 100644 --- a/govtool/frontend/src/components/atoms/StakeRadio.tsx +++ b/govtool/frontend/src/components/atoms/StakeRadio.tsx @@ -2,12 +2,12 @@ import { Dispatch, FC, SetStateAction } from "react"; import { Box, IconButton, Typography } from "@mui/material"; import { ICONS } from "@consts"; -import { theme } from "@/theme"; import { useGetAdaHolderVotingPowerQuery, useScreenDimension, useTranslation, } from "@hooks"; +import { theme } from "@/theme"; import { correctAdaFormat } from "@/utils/adaFormat"; type StakeRadioProps = { @@ -31,19 +31,19 @@ export const StakeRadio: FC = ({ ...props }) => { onChange(stakeKey)} sx={[{ "&:hover": { cursor: "pointer" } }]} > ` font-family: "Poppins"; - font-size: 16px; font-weight: 400; ::placeholder { font-family: "Poppins"; - font-size: 16px; font-weight: 400; color: #a6a6a6; } - ` + `, ); export const TextArea = forwardRef( - ({ errorMessage, maxLength = 500, onBlur, onFocus, ...props }, ref) => { + ( + { + errorMessage, + maxLength = 500, + onBlur, + onFocus, + isModifiedLayout, + ...props + }, + ref, + ) => { const { isMobile } = useScreenDimension(); const textAraeRef = useRef(null); @@ -29,7 +37,7 @@ export const TextArea = forwardRef( onFocus?.(e); textAraeRef.current?.focus(); }, - [] + [], ); const handleBlur = useCallback( @@ -37,7 +45,7 @@ export const TextArea = forwardRef( onBlur?.(e); textAraeRef.current?.blur(); }, - [] + [], ); useImperativeHandle( @@ -48,24 +56,37 @@ export const TextArea = forwardRef( blur: handleBlur, ...textAraeRef.current, } as unknown as HTMLTextAreaElement), - [handleBlur, handleFocus] + [handleBlur, handleFocus], ); + const getTexAreaHeight = () => { + if (isModifiedLayout && isMobile) return "312px"; + if (isModifiedLayout) return "208px"; + if (isMobile) return "104px"; + return "128px"; + }; + return ( ); - } + }, ); diff --git a/govtool/frontend/src/components/atoms/Tooltip.tsx b/govtool/frontend/src/components/atoms/Tooltip.tsx index a463dcd59..dd8d8dc23 100644 --- a/govtool/frontend/src/components/atoms/Tooltip.tsx +++ b/govtool/frontend/src/components/atoms/Tooltip.tsx @@ -1,55 +1,13 @@ import { styled } from "@mui/material"; import * as TooltipMUI from "@mui/material/Tooltip"; import Typography from "@mui/material/Typography"; - -type TooltipProps = Omit & { - heading?: string; - paragraphOne?: string; - paragraphTwo?: string; -}; - -export const Tooltip = ({ - heading, - paragraphOne, - paragraphTwo, - ...tooltipProps -}: TooltipProps) => { - return ( - - {heading && ( - - {heading} - - )} - - {paragraphOne && paragraphOne} - {paragraphTwo && ( - <> -

- {paragraphTwo} - - )} -
- - } - /> - ); -}; +import { TooltipProps } from "@atoms"; const StyledTooltip = styled( ({ className, ...props }: TooltipMUI.TooltipProps) => ( + // eslint-disable-next-line react/jsx-pascal-case - ) + ), )(() => ({ [`& .${TooltipMUI.tooltipClasses.arrow}`]: { color: "rgb(36, 34, 50)", @@ -59,3 +17,39 @@ const StyledTooltip = styled( padding: 12, }, })); + +export const Tooltip = ({ + heading, + paragraphOne, + paragraphTwo, + ...tooltipProps +}: TooltipProps) => ( + + {heading && ( + + {heading} + + )} + + {paragraphOne && paragraphOne} + {paragraphTwo && ( + <> +

+ {paragraphTwo} + + )} +
+ + } + /> +); diff --git a/govtool/frontend/src/components/atoms/VotePill.tsx b/govtool/frontend/src/components/atoms/VotePill.tsx index d16766d07..de29ac900 100644 --- a/govtool/frontend/src/components/atoms/VotePill.tsx +++ b/govtool/frontend/src/components/atoms/VotePill.tsx @@ -1,6 +1,7 @@ -import { Vote } from "@models"; import { Box, Typography } from "@mui/material"; +import { Vote } from "@models"; + export const VotePill = ({ vote, width, @@ -30,10 +31,10 @@ export const VotePill = ({ maxHeight="14px" > {vote} diff --git a/govtool/frontend/src/components/atoms/VotingPowerChips.tsx b/govtool/frontend/src/components/atoms/VotingPowerChips.tsx index ad707be93..4e5381e56 100644 --- a/govtool/frontend/src/components/atoms/VotingPowerChips.tsx +++ b/govtool/frontend/src/components/atoms/VotingPowerChips.tsx @@ -1,44 +1,45 @@ import { Box, CircularProgress } from "@mui/material"; import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined"; -import { Typography } from "@atoms"; +import { Typography, Tooltip } from "@atoms"; import { useCardano } from "@context"; import { useGetAdaHolderVotingPowerQuery, useGetDRepVotingPowerQuery, + useGetVoterInfo, useScreenDimension, useTranslation, } from "@hooks"; import { correctAdaFormat } from "@utils"; -import { Tooltip } from "@atoms"; export const VotingPowerChips = () => { - const { voter, stakeKey, isDrepLoading } = useCardano(); - const { dRepVotingPower, isDRepVotingPowerLoading } = - useGetDRepVotingPowerQuery(); - const { votingPower, powerIsLoading } = - useGetAdaHolderVotingPowerQuery(stakeKey); + const { stakeKey, isEnableLoading } = useCardano(); + const { dRepVotingPower } = useGetDRepVotingPowerQuery(); + const { votingPower } = useGetAdaHolderVotingPowerQuery(stakeKey); const { isMobile, screenWidth } = useScreenDimension(); const { t } = useTranslation(); + const { voter } = useGetVoterInfo(); return ( {voter?.isRegisteredAsDRep && ( { {t("votingPower")}
)} - {(voter?.isRegisteredAsDRep && isDRepVotingPowerLoading) || - (!voter?.isRegisteredAsDRep && powerIsLoading) || - isDrepLoading ? ( - + {(voter?.isRegisteredAsDRep && dRepVotingPower === undefined) || + (!voter?.isRegisteredAsDRep && votingPower === undefined) || + isEnableLoading || + !voter ? ( + ) : ( ->; +export type MuiModalChildren = ComponentProps["children"]; interface Props { open: boolean; @@ -13,10 +9,8 @@ interface Props { handleClose?: () => void; } -export function Modal({ open, children, handleClose }: Props) { - return ( - - <>{children} - - ); -} +export const Modal = ({ open, children, handleClose }: Props) => ( + + {children} + +); diff --git a/govtool/frontend/src/components/atoms/modal/ModalContents.tsx b/govtool/frontend/src/components/atoms/modal/ModalContents.tsx index fdfb04670..6ebe56ab7 100644 --- a/govtool/frontend/src/components/atoms/modal/ModalContents.tsx +++ b/govtool/frontend/src/components/atoms/modal/ModalContents.tsx @@ -1,11 +1,11 @@ -import { useScreenDimension } from "@/hooks"; import { Box } from "@mui/material"; +import { useScreenDimension } from "@/hooks"; interface Props { children: React.ReactNode; } -export function ModalContents({ children }: Props) { +export const ModalContents = ({ children }: Props) => { const { isMobile } = useScreenDimension(); return ( @@ -18,4 +18,4 @@ export function ModalContents({ children }: Props) { {children}
); -} +}; diff --git a/govtool/frontend/src/components/atoms/modal/ModalHeader.tsx b/govtool/frontend/src/components/atoms/modal/ModalHeader.tsx index b94913e9f..6a424e6b4 100644 --- a/govtool/frontend/src/components/atoms/modal/ModalHeader.tsx +++ b/govtool/frontend/src/components/atoms/modal/ModalHeader.tsx @@ -6,16 +6,14 @@ interface Props { sx?: SxProps; } -export function ModalHeader({ children, sx }: Props) { - return ( - - {children} - - ); -} +export const ModalHeader = ({ children, sx }: Props) => ( + + {children} + +); diff --git a/govtool/frontend/src/components/atoms/modal/ModalWrapper.tsx b/govtool/frontend/src/components/atoms/modal/ModalWrapper.tsx index 5300f0fd9..598b21e4b 100644 --- a/govtool/frontend/src/components/atoms/modal/ModalWrapper.tsx +++ b/govtool/frontend/src/components/atoms/modal/ModalWrapper.tsx @@ -13,31 +13,6 @@ interface Props { sx?: SxProps; } -export function ModalWrapper({ - children, - onClose, - variant = "modal", - hideCloseButton = false, - dataTestId = "modal", - sx, -}: Props) { - const { closeModal } = useModal(); - - return ( - - {variant !== "popup" && !hideCloseButton && ( - - )} - {children} - - ); -} - export const BaseWrapper = styled("div")>` box-shadow: 1px 2px 11px 0px #00123d5e; max-height: 90vh; @@ -73,3 +48,28 @@ export const CloseButton = styled("img")` top: 24px; right: 24px; `; + +export const ModalWrapper = ({ + children, + onClose, + variant = "modal", + hideCloseButton = false, + dataTestId = "modal", + sx, +}: Props) => { + const { closeModal } = useModal(); + + return ( + + {variant !== "popup" && !hideCloseButton && ( + + )} + {children} + + ); +}; diff --git a/govtool/frontend/src/components/atoms/types.ts b/govtool/frontend/src/components/atoms/types.ts index 34f0fdf14..2a02f519b 100644 --- a/govtool/frontend/src/components/atoms/types.ts +++ b/govtool/frontend/src/components/atoms/types.ts @@ -7,9 +7,11 @@ import { TextareaAutosizeProps, SxProps, } from "@mui/material"; +import * as TooltipMUI from "@mui/material/Tooltip"; export type ButtonProps = Omit & { size?: "small" | "medium" | "large" | "extraLarge"; + dataTestId?: string; }; export type LoadingButtonProps = ButtonProps & { @@ -65,9 +67,16 @@ export type FormHelpfulTextProps = { export type TextAreaProps = TextareaAutosizeProps & { errorMessage?: string; + isModifiedLayout?: boolean; }; export type InfoTextProps = { label: string; sx?: SxProps; }; + +export type TooltipProps = Omit & { + heading?: string; + paragraphOne?: string; + paragraphTwo?: string; +}; diff --git a/govtool/frontend/src/components/molecules/ActionCard.tsx b/govtool/frontend/src/components/molecules/ActionCard.tsx index 548af7091..6cab992d7 100644 --- a/govtool/frontend/src/components/molecules/ActionCard.tsx +++ b/govtool/frontend/src/components/molecules/ActionCard.tsx @@ -2,8 +2,8 @@ import { Box } from "@mui/material"; import { FC } from "react"; import { Button, Typography } from "@atoms"; -import { theme } from "@/theme"; import { useScreenDimension } from "@hooks"; +import { theme } from "@/theme"; type ActionCardProps = { description?: string; @@ -52,14 +52,19 @@ export const ActionCard: FC = ({ ...props }) => { > {imageURL ? ( - + action-card ) : null} {title ? ( {title} @@ -71,7 +76,7 @@ export const ActionCard: FC = ({ ...props }) => { mb: 4.25, mt: 1.75, }} - variant={"body1"} + variant="body1" > {description}
diff --git a/govtool/frontend/src/components/molecules/Breadcrumbs.tsx b/govtool/frontend/src/components/molecules/Breadcrumbs.tsx new file mode 100644 index 000000000..c0766d345 --- /dev/null +++ b/govtool/frontend/src/components/molecules/Breadcrumbs.tsx @@ -0,0 +1,61 @@ +import { NavLink, To } from "react-router-dom"; +import { Box } from "@mui/material"; +import Divider from "@mui/material/Divider"; + +import { useScreenDimension } from "@hooks"; +import { Typography } from "@atoms"; + +type BreadcrumbsProps = { + elementOne: string; + elementOnePath: To; + elementTwo: string; + isDataMissing: boolean; +}; + +export const Breadcrumbs = ({ + elementOne, + elementOnePath, + elementTwo, + isDataMissing, +}: BreadcrumbsProps) => { + const { isMobile } = useScreenDimension(); + + return ( + + + + {elementOne} + + + + + {isDataMissing || elementTwo} + + + ); +}; diff --git a/govtool/frontend/src/components/molecules/Card.tsx b/govtool/frontend/src/components/molecules/Card.tsx index 784c5a747..a6ef31177 100644 --- a/govtool/frontend/src/components/molecules/Card.tsx +++ b/govtool/frontend/src/components/molecules/Card.tsx @@ -1,7 +1,7 @@ import { Chip, Paper, SxProps } from "@mui/material"; +import { Theme } from "@mui/material/styles"; import { PropsWithChildren } from "react"; -import { Theme } from "@/theme"; import { errorRed, orange, primaryBlue, successGreen } from "@/consts"; type CardProps = PropsWithChildren & { @@ -12,14 +12,37 @@ type CardProps = PropsWithChildren & { variant?: "default" | "error" | "primary" | "success" | "warning"; }; -export function Card({ +const COLORS = { + default: { + backgroundColor: undefined, + borderColor: primaryBlue.c100, + }, + warning: { + backgroundColor: undefined, + borderColor: orange.c500, + }, + error: { + backgroundColor: `${errorRed.c50}80`, + borderColor: errorRed.c100, + }, + primary: { + backgroundColor: `${primaryBlue.c100}40`, + borderColor: primaryBlue.c500, + }, + success: { + backgroundColor: undefined, + borderColor: successGreen.c500, + }, +} as const; + +export const Card = ({ variant = "default", border = variant !== "default", children, - elevation = 4, + elevation = 3, label, sx, -}: CardProps) { +}: CardProps) => { const colors = COLORS[variant]; return ( @@ -49,27 +72,4 @@ export function Card({ {children} ); -} - -const COLORS = { - default: { - backgroundColor: undefined, - borderColor: primaryBlue.c100, - }, - warning: { - backgroundColor: undefined, - borderColor: orange.c500, - }, - error: { - backgroundColor: `${errorRed.c50}80`, - borderColor: errorRed.c100, - }, - primary: { - backgroundColor: `${primaryBlue.c100}40`, - borderColor: primaryBlue.c500, - }, - success: { - backgroundColor: undefined, - borderColor: successGreen.c500, - }, -} as const; +}; diff --git a/govtool/frontend/src/components/molecules/CenteredBoxBottomButtons.tsx b/govtool/frontend/src/components/molecules/CenteredBoxBottomButtons.tsx index 5ca687317..101787797 100644 --- a/govtool/frontend/src/components/molecules/CenteredBoxBottomButtons.tsx +++ b/govtool/frontend/src/components/molecules/CenteredBoxBottomButtons.tsx @@ -22,10 +22,10 @@ export const CenteredBoxBottomButtons = ({ const { isMobile } = useScreenDimension(); const { t } = useTranslation(); - const renderBackButton = useMemo(() => { - return ( + const renderBackButton = useMemo( + () => ( - ); - }, [isMobile]); + ), + [isMobile], + ); - const renderActionButton = useMemo(() => { - return ( + const renderActionButton = useMemo( + () => ( {actionButtonText ?? t("continue")} - ); - }, [isLoading, isMobile]); + ), + [isLoading, isMobile], + ); return ( > = ({ return ( - + > = ({ {backButtonText}
- + diff --git a/govtool/frontend/src/components/molecules/CopyableInfo.tsx b/govtool/frontend/src/components/molecules/CopyableInfo.tsx new file mode 100644 index 000000000..792aabc72 --- /dev/null +++ b/govtool/frontend/src/components/molecules/CopyableInfo.tsx @@ -0,0 +1,49 @@ +import { Box, SxProps, Typography } from "@mui/material"; + +import { CopyButton } from "@atoms"; +import { gray } from "@consts"; + +import { Card } from "./Card"; + +type CopyableInfoProps = { + dataTestId?: string; + label: string; + value: string; + sx?: SxProps; +}; + +export const CopyableInfo = ({ + dataTestId, + label, + value, + sx, +}: CopyableInfoProps) => ( + theme.palette.neutralWhite, + ...sx, + }} + > + + {label} + + + + {value} + + + + +); diff --git a/govtool/frontend/src/components/molecules/DRepInfoCard.tsx b/govtool/frontend/src/components/molecules/DRepInfoCard.tsx index e438f0b63..00663ea63 100644 --- a/govtool/frontend/src/components/molecules/DRepInfoCard.tsx +++ b/govtool/frontend/src/components/molecules/DRepInfoCard.tsx @@ -3,31 +3,33 @@ import { Box, Typography } from "@mui/material"; import { useCardano } from "@context"; import { CopyButton } from "@atoms"; import { useTranslation } from "@hooks"; +import { Card } from "./Card"; +import { gray } from "@/consts"; export const DRepInfoCard = () => { const { dRepIDBech32 } = useCardano(); const { t } = useTranslation(); return ( - + - + {t("myDRepId")} {dRepIDBech32} - + ); }; diff --git a/govtool/frontend/src/components/molecules/DashboardActionCard.tsx b/govtool/frontend/src/components/molecules/DashboardActionCard.tsx index 26bd46e98..feb9f39fb 100644 --- a/govtool/frontend/src/components/molecules/DashboardActionCard.tsx +++ b/govtool/frontend/src/components/molecules/DashboardActionCard.tsx @@ -1,31 +1,22 @@ -import { Box, ButtonProps, Skeleton } from "@mui/material"; +import { Box, Skeleton } from "@mui/material"; import { FC, ReactNode } from "react"; -import { CopyButton, LoadingButton, Typography } from "@atoms"; +import { Button, LoadingButton, LoadingButtonProps, Typography } from "@atoms"; import { useScreenDimension, useTranslation } from "@hooks"; -import { theme } from "@/theme"; +import { Card } from "./Card"; +import { openInNewTab } from "@/utils"; -type DashboardActionCardProps = { - cardId?: string; - cardTitle?: string; +export type DashboardActionCardProps = { + buttons?: LoadingButtonProps[]; + children?: ReactNode; dataTestidDelegationStatus?: string; - dataTestidDrepIdBox?: string; - dataTestidFirstButton?: string; - dataTestidSecondButton?: string; description?: ReactNode; - firstButtonAction?: () => void; - firstButtonDisabled?: boolean; - firstButtonIsLoading?: boolean; - firstButtonLabel?: string; - firstButtonVariant?: ButtonProps["variant"]; imageURL?: string; - inProgress?: boolean; isLoading?: boolean; - secondButtonAction?: () => void; - secondButtonIsLoading?: boolean; - secondButtonLabel?: string; - secondButtonVariant?: ButtonProps["variant"]; + isInProgressOnCard?: boolean; + state?: "active" | "inProgress" | "default"; title?: ReactNode; + transactionId?: string; }; export const DashboardActionCard: FC = ({ @@ -33,63 +24,38 @@ export const DashboardActionCard: FC = ({ }) => { const { t } = useTranslation(); const { - cardId, - cardTitle, - dataTestidDrepIdBox, - dataTestidFirstButton, - dataTestidSecondButton, + buttons, + children, description, - firstButtonAction, - firstButtonDisabled = false, - firstButtonIsLoading = false, - firstButtonLabel, - firstButtonVariant = "contained", imageURL, - inProgress, isLoading = false, - secondButtonAction, - secondButtonIsLoading = false, - secondButtonLabel, - secondButtonVariant = "outlined", + isInProgressOnCard = true, + state = "default", title, + transactionId, } = props; - const { - palette: { boxShadow2 }, - } = theme; - const { isMobile, screenWidth } = useScreenDimension(); + const { screenWidth } = useScreenDimension(); + + const onClickShowTransaction = () => + openInNewTab(`https://sancho.cexplorer.io/tx/${transactionId}`); return ( - - {inProgress && !isLoading && ( - - - {t("inProgress")} - - - )} - + {imageURL ? ( isLoading ? ( = ({ /> ) : ( card = ({ {title ? ( {isLoading ? : title} - - ) : null} - {inProgress && !isLoading ? ( - - {t("inProgress")} + {state === "inProgress" && !isLoading && isInProgressOnCard ? ( + + {` ${t("inProgress")}`} + + ) : null} ) : null} {description ? ( @@ -132,102 +103,57 @@ export const DashboardActionCard: FC = ({ )} ) : null} - {cardId && ( - - - - {cardTitle} - - - {cardId} - - - - - )} - {isLoading ? ( - - - - - ) : ( - - {firstButtonLabel ? ( - - {firstButtonLabel} - - ) : null} - {secondButtonLabel ? ( + {t("dashboard.cards.showTransaction")} + + )} + + {isLoading ? ( + <> + + + + ) : ( + buttons?.map(({ dataTestId, ...buttonProps }) => ( - {secondButtonLabel} - - ) : null} - - )} - + {...buttonProps} + /> + )) + )} + + ); }; diff --git a/govtool/frontend/src/components/molecules/DataActionsBar.tsx b/govtool/frontend/src/components/molecules/DataActionsBar.tsx index 86613a8bc..f8f1587c5 100644 --- a/govtool/frontend/src/components/molecules/DataActionsBar.tsx +++ b/govtool/frontend/src/components/molecules/DataActionsBar.tsx @@ -2,10 +2,8 @@ import { Dispatch, FC, SetStateAction } from "react"; import { Box, InputBase } from "@mui/material"; import Search from "@mui/icons-material/Search"; -import { GovernanceActionsFilters, GovernanceActionsSorting } from "."; +import { DataActionsFilters, DataActionsSorting } from "@molecules"; import { OrderActionsChip } from "./OrderActionsChip"; -import { ClickOutside } from "../atoms"; - import { theme } from "@/theme"; type DataActionsBarProps = { @@ -14,7 +12,12 @@ type DataActionsBarProps = { chosenSorting: string; closeFilters?: () => void; closeSorts: () => void; + filterOptions?: { + key: string; + label: string; + }[]; filtersOpen?: boolean; + filtersTitle?: string; isFiltering?: boolean; searchText: string; setChosenFilters?: Dispatch>; @@ -24,6 +27,10 @@ type DataActionsBarProps = { setSortOpen: Dispatch>; sortingActive: boolean; sortOpen: boolean; + sortOptions?: { + key: string; + label: string; + }[]; }; export const DataActionsBar: FC = ({ ...props }) => { @@ -33,7 +40,9 @@ export const DataActionsBar: FC = ({ ...props }) => { chosenSorting, closeFilters = () => {}, closeSorts, + filterOptions = [], filtersOpen, + filtersTitle, isFiltering = true, searchText, setChosenFilters = () => {}, @@ -43,6 +52,7 @@ export const DataActionsBar: FC = ({ ...props }) => { setSortOpen, sortingActive, sortOpen, + sortOptions = [], } = props; const { palette: { boxShadow2 }, @@ -50,7 +60,7 @@ export const DataActionsBar: FC = ({ ...props }) => { return ( <> - + setSearchText(e.target.value)} @@ -76,7 +86,7 @@ export const DataActionsBar: FC = ({ ...props }) => { fontWeight: 500, height: 48, padding: "16px 24px", - width: 231, + width: 500, }} /> = ({ ...props }) => { setSortOpen={setSortOpen} sortingActive={sortingActive} sortOpen={sortOpen} - /> + > + {filtersOpen && ( + + )} + {sortOpen && ( + + )} + - {filtersOpen && ( - - - - )} - {sortOpen && ( - - - - )} ); }; diff --git a/govtool/frontend/src/components/molecules/DataActionsFilters.tsx b/govtool/frontend/src/components/molecules/DataActionsFilters.tsx new file mode 100644 index 000000000..98ec187e3 --- /dev/null +++ b/govtool/frontend/src/components/molecules/DataActionsFilters.tsx @@ -0,0 +1,112 @@ +import { Dispatch, SetStateAction, useCallback, useRef } from "react"; +import { + Box, + Checkbox, + FormControlLabel, + FormLabel, + Typography, +} from "@mui/material"; + +import { useOnClickOutside, useScreenDimension } from "@hooks"; + +interface Props { + chosenFilters: string[]; + setChosenFilters: Dispatch>; + closeFilters: () => void; + options: { + key: string; + label: string; + }[]; + title?: string; +} + +export const DataActionsFilters = ({ + chosenFilters, + setChosenFilters, + closeFilters, + options, + title, +}: Props) => { + const handleFilterChange = useCallback( + (e: React.ChangeEvent) => { + // TODO: Refine if it is needed to remove this eslint-disable + // eslint-disable-next-line no-unused-expressions, no-sequences + e.target.name, e.target.checked; + let filters = [...chosenFilters]; + if (e.target.checked) { + filters.push(e.target.name); + } else { + filters = filters.filter((str) => str !== e.target.name); + } + setChosenFilters(filters); + }, + [chosenFilters, setChosenFilters], + ); + + const { isMobile, screenWidth } = useScreenDimension(); + + const wrapperRef = useRef(null); + useOnClickOutside(wrapperRef, closeFilters); + + return ( + + {title && ( + + {title} + + )} + {options.map((item) => ( + + + } + label={ + + {item.label} + + } + /> + + ))} + + ); +}; diff --git a/govtool/frontend/src/components/molecules/DataActionsSorting.tsx b/govtool/frontend/src/components/molecules/DataActionsSorting.tsx new file mode 100644 index 000000000..65487da1d --- /dev/null +++ b/govtool/frontend/src/components/molecules/DataActionsSorting.tsx @@ -0,0 +1,96 @@ +import { Dispatch, SetStateAction, useRef } from "react"; +import { + Box, + FormControl, + FormControlLabel, + Radio, + RadioGroup, + Typography, +} from "@mui/material"; + +import { useTranslation, useOnClickOutside } from "@hooks"; + +interface Props { + chosenSorting: string; + setChosenSorting: Dispatch>; + closeSorts: () => void; + options: { + key: string; + label: string; + }[]; +} + +export const DataActionsSorting = ({ + chosenSorting, + setChosenSorting, + closeSorts, + options, +}: Props) => { + const { t } = useTranslation(); + + const wrapperRef = useRef(null); + useOnClickOutside(wrapperRef, closeSorts); + + return ( + + + + + {t("sortBy")} + + setChosenSorting("")}> + + {t("clear")} + + + + { + setChosenSorting(e.target.value); + }} + > + {options.map((item) => ( + + } + label={item.label} + /> + ))} + + + + ); +}; diff --git a/govtool/frontend/src/components/molecules/DataMissingInfoBox.tsx b/govtool/frontend/src/components/molecules/DataMissingInfoBox.tsx new file mode 100644 index 000000000..f13da7ec2 --- /dev/null +++ b/govtool/frontend/src/components/molecules/DataMissingInfoBox.tsx @@ -0,0 +1,84 @@ +import { Box, Link } from "@mui/material"; + +import { Typography } from "@atoms"; +import { useTranslation } from "@hooks"; +import { GAMetedataErrors, openInNewTab } from "@utils"; + +export const DataMissingInfoBox = ({ + isDataMissing, + isInProgress, + isSubmitted, +}: { + isDataMissing: boolean | GAMetedataErrors; + isInProgress?: boolean; + isSubmitted?: boolean; +}) => { + const { t } = useTranslation(); + + const gaMetadataErrorMessage = { + [GAMetedataErrors.DATA_MISSING]: t("errors.gAMetadata.message.dataMissing"), + [GAMetedataErrors.INCORRECT_FORMAT]: t( + "errors.gAMetadata.message.incorrectFormat", + ), + [GAMetedataErrors.NOT_VERIFIABLE]: t( + "errors.gAMetadata.message.notVerifiable", + ), + }[isDataMissing as GAMetedataErrors]; + + const gaMetadataErrorDescription = { + [GAMetedataErrors.DATA_MISSING]: t( + "errors.gAMetadata.description.dataMissing", + ), + [GAMetedataErrors.INCORRECT_FORMAT]: t( + "errors.gAMetadata.description.incorrectFormat", + ), + [GAMetedataErrors.NOT_VERIFIABLE]: t( + "errors.gAMetadata.description.notVerifiable", + ), + }[isDataMissing as GAMetedataErrors]; + + return isDataMissing && !isSubmitted && !isInProgress ? ( + + + {gaMetadataErrorMessage} + + + {gaMetadataErrorDescription} + + + // TODO: Add the correct link + openInNewTab( + "https://docs.sanchogov.tools/how-to-use-the-govtool/getting-started/get-a-compatible-wallet", + ) + } + sx={{ + fontFamily: "Poppins", + fontSize: "16px", + lineHeight: "24px", + cursor: "pointer", + }} + > + {t("learnMore")} + + + ) : null; +}; diff --git a/govtool/frontend/src/components/molecules/EmptyStateGovernanceActionsCategory.tsx b/govtool/frontend/src/components/molecules/EmptyStateGovernanceActionsCategory.tsx new file mode 100644 index 000000000..5ed979ac8 --- /dev/null +++ b/govtool/frontend/src/components/molecules/EmptyStateGovernanceActionsCategory.tsx @@ -0,0 +1,40 @@ +import { Typography } from "@atoms"; +import { useTranslation } from "@hooks"; +import { getProposalTypeLabel } from "@utils"; + +import { EmptyStateGovernanceActionsCategoryProps } from "./types"; + +export const EmptyStateGovernanceActionsCategory = ({ + category, + isSearch, +}: EmptyStateGovernanceActionsCategoryProps) => { + const { t } = useTranslation(); + + return ( + + {isSearch ? ( + t("govActions.noResultsForTheSearch") + ) : ( + <> + {t("govActions.withCategoryNotExist.partOne")} +   + + {getProposalTypeLabel(category ?? "")} + +   + {t("govActions.withCategoryNotExist.partTwo")} + + )} + + ); +}; diff --git a/govtool/frontend/src/components/molecules/Field/Checkbox.tsx b/govtool/frontend/src/components/molecules/Field/Checkbox.tsx index 26b8aabab..2495ca810 100644 --- a/govtool/frontend/src/components/molecules/Field/Checkbox.tsx +++ b/govtool/frontend/src/components/molecules/Field/Checkbox.tsx @@ -2,8 +2,8 @@ import { Box } from "@mui/material"; import { Checkbox as CheckboxBase, FormErrorMessage, Typography } from "@atoms"; -import { CheckboxFieldProps } from "./types"; import { useCallback } from "react"; +import { CheckboxFieldProps } from "./types"; export const Checkbox = ({ errorMessage, diff --git a/govtool/frontend/src/components/molecules/Field/Input.tsx b/govtool/frontend/src/components/molecules/Field/Input.tsx index 58dbe521e..6982ae6a7 100644 --- a/govtool/frontend/src/components/molecules/Field/Input.tsx +++ b/govtool/frontend/src/components/molecules/Field/Input.tsx @@ -24,7 +24,7 @@ export const Input = forwardRef( onFocus, ...rest }, - ref + ref, ) => { const inputRef = useRef(null); @@ -46,7 +46,7 @@ export const Input = forwardRef( blur: handleBlur, ...inputRef.current, } as unknown as HTMLInputElement), - [handleBlur, handleFocus] + [handleBlur, handleFocus], ); return ( @@ -72,5 +72,5 @@ export const Input = forwardRef( /> ); - } + }, ); diff --git a/govtool/frontend/src/components/molecules/Field/TextArea.tsx b/govtool/frontend/src/components/molecules/Field/TextArea.tsx index 73b91be86..177c87a2e 100644 --- a/govtool/frontend/src/components/molecules/Field/TextArea.tsx +++ b/govtool/frontend/src/components/molecules/Field/TextArea.tsx @@ -7,8 +7,8 @@ import { Typography, } from "@atoms"; -import { TextAreaFieldProps } from "./types"; import { forwardRef, useCallback, useImperativeHandle, useRef } from "react"; +import { TextAreaFieldProps } from "./types"; export const TextArea = forwardRef( ( @@ -25,7 +25,7 @@ export const TextArea = forwardRef( onFocus, ...props }, - ref + ref, ) => { const textAreaRef = useRef(null); @@ -34,7 +34,7 @@ export const TextArea = forwardRef( onFocus?.(e); textAreaRef.current?.focus(); }, - [] + [], ); const handleBlur = useCallback( @@ -42,7 +42,7 @@ export const TextArea = forwardRef( onBlur?.(e); textAreaRef.current?.blur(); }, - [] + [], ); useImperativeHandle( @@ -53,8 +53,9 @@ export const TextArea = forwardRef( blur: handleBlur, ...textAreaRef.current, } as unknown as HTMLTextAreaElement), - [handleBlur, handleFocus] + [handleBlur, handleFocus], ); + return ( ( {label} )} - + + + + {props?.value?.toString()?.length ?? 0}/{maxLength} + + ( errorMessage={errorMessage} errorStyles={errorStyles} /> - - {props?.value?.toString()?.length ?? 0}/{maxLength} - ); - } + }, ); diff --git a/govtool/frontend/src/components/molecules/Field/index.tsx b/govtool/frontend/src/components/molecules/Field/index.tsx index b954e18b1..4065b89fa 100644 --- a/govtool/frontend/src/components/molecules/Field/index.tsx +++ b/govtool/frontend/src/components/molecules/Field/index.tsx @@ -10,9 +10,7 @@ type FieldComposition = React.FC & { TextArea: typeof TextArea; }; -const Field: FieldComposition = ({ children }) => { - return {children}; -}; +const Field: FieldComposition = ({ children }) => children; Field.Checkbox = Checkbox; Field.Input = Input; diff --git a/govtool/frontend/src/components/molecules/GovActionDetails.tsx b/govtool/frontend/src/components/molecules/GovActionDetails.tsx index bbf49932e..87fdf76b8 100644 --- a/govtool/frontend/src/components/molecules/GovActionDetails.tsx +++ b/govtool/frontend/src/components/molecules/GovActionDetails.tsx @@ -5,6 +5,7 @@ export const GovActionDetails = ({ value, }: { title: string; + // eslint-disable-next-line @typescript-eslint/no-explicit-any value: any; }) => { if (Array.isArray(value)) { @@ -13,6 +14,8 @@ export const GovActionDetails = ({ {title}:
    {value.map((item, index) => ( + // TODO: Get rid of index as key - ref: https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/no-array-index-key.md + // eslint-disable-next-line react/no-array-index-key
  • ); - } else if (typeof value === "boolean") { - return ( - - {title}: {value ? "True" : "False"} - - ); - } else { + } + if (typeof value === "boolean") { return ( - {title}: {value} + {title}:{value ? "True" : "False"} ); } + return ( + + {title}:{value} + + ); }; diff --git a/govtool/frontend/src/components/molecules/GovernanceActionCard.tsx b/govtool/frontend/src/components/molecules/GovernanceActionCard.tsx index 56eb7429f..e49b58837 100644 --- a/govtool/frontend/src/components/molecules/GovernanceActionCard.tsx +++ b/govtool/frontend/src/components/molecules/GovernanceActionCard.tsx @@ -1,232 +1,128 @@ import { FC } from "react"; import { Box } from "@mui/material"; -import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined"; -import { Button, Typography, Tooltip } from "@atoms"; +import { Button } from "@atoms"; +import { + GovernanceActionCardElement, + GovernanceActionCardHeader, + GovernanceActionCardStatePill, + GovernanceActionsDatesBox, +} from "@molecules"; + import { useScreenDimension, useTranslation } from "@hooks"; -import { theme } from "@/theme"; import { formatDisplayDate, getFullGovActionId, getProposalTypeLabel, - getShortenedGovActionId, + getProposalTypeNoEmptySpaces, } from "@utils"; -interface ActionTypeProps - extends Omit< - ActionType, - | "yesVotes" - | "noVotes" - | "abstainVotes" - | "metadataHash" - | "url" - | "details" - | "id" - | "txHash" - | "index" - > { +type ActionTypeProps = Omit< + ActionTypeToDsiplay, + | "yesVotes" + | "noVotes" + | "abstainVotes" + | "metadataHash" + | "url" + | "id" + | "details" + | "rationale" + | "motivation" +> & { onClick?: () => void; inProgress?: boolean; - txHash: string; - index: number; -} +}; export const GovernanceActionCard: FC = ({ ...props }) => { const { type, inProgress = false, expiryDate, + expiryEpochNo, onClick, createdDate, + createdEpochNo, txHash, index, + isDataMissing, + title, + about, } = props; const { isMobile, screenWidth } = useScreenDimension(); const { t } = useTranslation(); - const { - palette: { lightBlue }, - } = theme; - const govActionId = getFullGovActionId(txHash, index); - const proposalTypeNoEmptySpaces = getProposalTypeLabel(type).replace( - / /g, - "" - ); return ( - {inProgress && ( - - - {t("inProgress")} - - - )} + {inProgress && } - - - {t("govActions.governanceActionType")} - - - - - {getProposalTypeLabel(type)} - - - - - - - {t("govActions.governanceActionId")} - - - - - {getShortenedGovActionId(txHash, index)} - - - - + + + + + - {createdDate ? ( - - - {t("govActions.submissionDate")} - - - {formatDisplayDate(createdDate)} - - - - - - ) : null} - {expiryDate ? ( - - - {t("govActions.expiryDate")} - - - {formatDisplayDate(expiryDate)} - - - - - - ) : null} diff --git a/govtool/frontend/src/components/molecules/GovernanceActionCardElement.tsx b/govtool/frontend/src/components/molecules/GovernanceActionCardElement.tsx new file mode 100644 index 000000000..83aa5f585 --- /dev/null +++ b/govtool/frontend/src/components/molecules/GovernanceActionCardElement.tsx @@ -0,0 +1,142 @@ +import { Box } from "@mui/material"; +import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined"; + +import { Typography, Tooltip, CopyButton, TooltipProps } from "@atoms"; + +type BaseProps = { + label: string; + text?: string; + dataTestId?: string; + isSliderCard?: boolean; + tooltipProps?: Omit; + marginBottom?: number; +}; + +type PillVariantProps = BaseProps & { + textVariant: "pill"; + isCopyButton?: false; +}; + +type OtherVariantsProps = BaseProps & { + textVariant?: "oneLine" | "twoLines" | "longText"; + isCopyButton?: boolean; +}; + +type GovernanceActionCardElementProps = PillVariantProps | OtherVariantsProps; + +export const GovernanceActionCardElement = ({ + label, + text, + dataTestId, + isSliderCard, + textVariant = "oneLine", + isCopyButton, + tooltipProps, + marginBottom, +}: GovernanceActionCardElementProps) => { + if (!text) { + return null; + } + return ( + + + + {label} + + {tooltipProps && ( + + + + )} + + + {textVariant === "pill" ? ( + + + {text} + + + ) : ( + + + {text} + + {isCopyButton && ( + + + + )} + + )} + + + ); +}; diff --git a/govtool/frontend/src/components/molecules/GovernanceActionCardHeader.tsx b/govtool/frontend/src/components/molecules/GovernanceActionCardHeader.tsx new file mode 100644 index 000000000..78747677d --- /dev/null +++ b/govtool/frontend/src/components/molecules/GovernanceActionCardHeader.tsx @@ -0,0 +1,55 @@ +import { Box } from "@mui/material"; +import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined"; + +import { Tooltip, Typography } from "@atoms"; +import { useTranslation } from "@hooks"; + +type GovernanceActionCardHeaderProps = { + title?: string; + isDataMissing: string | boolean; +}; + +export const GovernanceActionCardHeader = ({ + title, + isDataMissing, +}: GovernanceActionCardHeaderProps) => { + const { t } = useTranslation(); + + return ( + + + {isDataMissing || title} + + {isDataMissing && typeof isDataMissing === "string" && ( + + + + )} + + ); +}; diff --git a/govtool/frontend/src/components/molecules/GovernanceActionCardMyVote.tsx b/govtool/frontend/src/components/molecules/GovernanceActionCardMyVote.tsx new file mode 100644 index 000000000..c1dde83ec --- /dev/null +++ b/govtool/frontend/src/components/molecules/GovernanceActionCardMyVote.tsx @@ -0,0 +1,60 @@ +import { Box } from "@mui/material"; + +import { Button, Typography, VotePill } from "@atoms"; +import { openInNewTab } from "@utils"; +import { useTranslation } from "@hooks"; +import { Vote } from "@models"; + +type Props = { + vote: Vote; +}; + +export const GovernanceActionCardMyVote = ({ vote }: Props) => { + const { t } = useTranslation(); + + return ( + + + {t("govActions.myVote")} + + + + + + + + + ); +}; diff --git a/govtool/frontend/src/components/molecules/GovernanceActionCardStatePill.tsx b/govtool/frontend/src/components/molecules/GovernanceActionCardStatePill.tsx new file mode 100644 index 000000000..bba17071c --- /dev/null +++ b/govtool/frontend/src/components/molecules/GovernanceActionCardStatePill.tsx @@ -0,0 +1,51 @@ +import { Box } from "@mui/material"; +import CheckIcon from "@mui/icons-material/Check"; + +import { Typography } from "@atoms"; +import { useTranslation } from "@hooks"; + +export const GovernanceActionCardStatePill = ({ + variant = "voteSubmitted", +}: { + variant?: "inProgress" | "voteSubmitted"; +}) => { + const { t } = useTranslation(); + + return ( + + + {variant === "voteSubmitted" && ( + + )} + {variant === "inProgress" + ? t("inProgress") + : t("govActions.voteSubmitted")} + + + ); +}; diff --git a/govtool/frontend/src/components/molecules/GovernanceActionDetailsCardHeader.tsx b/govtool/frontend/src/components/molecules/GovernanceActionDetailsCardHeader.tsx new file mode 100644 index 000000000..6f5a7a22a --- /dev/null +++ b/govtool/frontend/src/components/molecules/GovernanceActionDetailsCardHeader.tsx @@ -0,0 +1,56 @@ +import { useLocation } from "react-router-dom"; +import { Box } from "@mui/material"; + +import { Typography } from "@atoms"; +import { Share } from "@molecules"; +import { GAMetedataErrors } from "@utils"; + +type GovernanceActionDetailsCardHeaderProps = { + title?: string; + isDataMissing: boolean | GAMetedataErrors; +}; + +export const GovernanceActionDetailsCardHeader = ({ + title, + isDataMissing, +}: GovernanceActionDetailsCardHeaderProps) => { + const { pathname, hash } = useLocation(); + + const govActionLinkToShare = `${window.location.protocol}//${ + window.location.hostname + }${window.location.port ? `:${window.location.port}` : ""}${pathname}${hash}`; + + return ( + + + + {isDataMissing || title} + + + + + ); +}; diff --git a/govtool/frontend/src/components/molecules/GovernanceActionDetailsCardLinks.tsx b/govtool/frontend/src/components/molecules/GovernanceActionDetailsCardLinks.tsx new file mode 100644 index 000000000..6d08028a6 --- /dev/null +++ b/govtool/frontend/src/components/molecules/GovernanceActionDetailsCardLinks.tsx @@ -0,0 +1,66 @@ +import { Box } from "@mui/material"; + +import { Typography } from "@atoms"; +import { useScreenDimension, useTranslation } from "@hooks"; +import { LinkWithIcon } from "@molecules"; +import { ICONS } from "@/consts"; +import { useModal } from "@/context"; + +// TODO: When BE is ready, pass links as props +const LINKS = [ + "https://docs.sanchogov.tools/support/get-help-in-discord", + "https://docs.sanchogov.tools/how-to-use-the-govtool/prerequsites", + "https://docs.sanchogov.tools/faqs", + "https://docs.sanchogov.tools/", +]; + +export const GovernanceActionDetailsCardLinks = () => { + const { isMobile } = useScreenDimension(); + const { t } = useTranslation(); + const { openModal } = useModal(); + + return ( + <> + + {t("govActions.supportingLinks")} + + + {LINKS.map((link) => ( + { + openModal({ + type: "externalLink", + state: { + externalLink: link, + }, + }); + }} + icon={link} + cutWithEllipsis + /> + ))} + + + ); +}; diff --git a/govtool/frontend/src/components/molecules/GovernanceActionDetailsCardOnChainData.tsx b/govtool/frontend/src/components/molecules/GovernanceActionDetailsCardOnChainData.tsx new file mode 100644 index 000000000..8cb0230c2 --- /dev/null +++ b/govtool/frontend/src/components/molecules/GovernanceActionDetailsCardOnChainData.tsx @@ -0,0 +1,74 @@ +import { Box } from "@mui/material"; + +import { Typography } from "@atoms"; +import { useTranslation } from "@hooks"; + +type GovernanceActionDetailsCardOnChainDataProps = { + data: ActionDetailsType; +}; + +export const GovernanceActionDetailsCardOnChainData = ({ + data, +}: GovernanceActionDetailsCardOnChainDataProps) => { + const { t } = useTranslation(); + + return ( + + + + {t("govActions.onChainTransactionDetails")} + + + {Object.entries(data).map(([label, content]) => ( + + + {label}: + + + {content} + + + ))} + + ); +}; diff --git a/govtool/frontend/src/components/molecules/GovernanceActionDetailsCardVotes.tsx b/govtool/frontend/src/components/molecules/GovernanceActionDetailsCardVotes.tsx new file mode 100644 index 000000000..ec6342f25 --- /dev/null +++ b/govtool/frontend/src/components/molecules/GovernanceActionDetailsCardVotes.tsx @@ -0,0 +1,76 @@ +import { Dispatch, SetStateAction } from "react"; +import { Box } from "@mui/material"; + +import { useScreenDimension } from "@hooks"; +import { VoteActionForm, VotesSubmitted } from "@molecules"; + +type GovernanceActionCardVotesProps = { + setIsVoteSubmitted: Dispatch>; + abstainVotes: number; + noVotes: number; + yesVotes: number; + isOneColumn: boolean; + expiryDate: string; + expiryEpochNo: number; + isVoter?: boolean; + voteFromEP?: string; + voteUrlFromEP?: string; + voteDateFromEP?: string; + voteEpochNoFromEP?: number; + isDashboard?: boolean; + isInProgress?: boolean; +}; + +export const GovernanceActionDetailsCardVotes = ({ + setIsVoteSubmitted, + abstainVotes, + noVotes, + yesVotes, + isOneColumn, + expiryDate, + expiryEpochNo, + isVoter, + voteFromEP, + voteUrlFromEP, + voteDateFromEP, + voteEpochNoFromEP, + isDashboard, + isInProgress, +}: GovernanceActionCardVotesProps) => { + const { screenWidth } = useScreenDimension(); + + const isModifiedPadding = + (isDashboard && screenWidth < 1368) ?? screenWidth < 1100; + + return ( + + {isVoter ? ( + + ) : ( + + )} + + ); +}; diff --git a/govtool/frontend/src/components/molecules/GovernanceActionsDatesBox.tsx b/govtool/frontend/src/components/molecules/GovernanceActionsDatesBox.tsx new file mode 100644 index 000000000..0e302b21d --- /dev/null +++ b/govtool/frontend/src/components/molecules/GovernanceActionsDatesBox.tsx @@ -0,0 +1,128 @@ +import { Box } from "@mui/material"; +import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined"; +import { Trans } from "react-i18next"; + +import { Tooltip, Typography } from "@atoms"; +import { useScreenDimension, useTranslation } from "@hooks"; + +type GovernanceActionsDatesBoxProps = { + createdDate: string; + expiryDate: string; + expiryEpochNo: number; + createdEpochNo: number; + isSliderCard?: boolean; +}; + +export const GovernanceActionsDatesBox = ({ + createdDate, + expiryDate, + expiryEpochNo, + createdEpochNo, + isSliderCard, +}: GovernanceActionsDatesBoxProps) => { + const { t } = useTranslation(); + const { screenWidth } = useScreenDimension(); + + const isFontSizeSmaller = screenWidth < 420; + + return ( + + + + , + , + ]} + /> + + + + + + + + , + , + ]} + /> + + + + + + + ); +}; diff --git a/govtool/frontend/src/components/molecules/GovernanceActionsFilters.tsx b/govtool/frontend/src/components/molecules/GovernanceActionsFilters.tsx deleted file mode 100644 index ca8ae69d4..000000000 --- a/govtool/frontend/src/components/molecules/GovernanceActionsFilters.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import { Dispatch, SetStateAction, useCallback } from "react"; -import { - Box, - Checkbox, - FormControlLabel, - FormLabel, - Typography, -} from "@mui/material"; - -import { GOVERNANCE_ACTIONS_FILTERS } from "@consts"; -import { useTranslation } from "@hooks"; - -interface Props { - chosenFilters: string[]; - setChosenFilters: Dispatch>; -} - -export const GovernanceActionsFilters = ({ - chosenFilters, - setChosenFilters, -}: Props) => { - const handleFilterChange = useCallback( - (e: React.ChangeEvent) => { - e.target.name, e.target.checked; - let filters = [...chosenFilters]; - if (e.target.checked) { - filters.push(e.target.name); - } else { - filters = filters.filter((str) => str !== e.target.name); - } - setChosenFilters(filters); - }, - [chosenFilters, setChosenFilters] - ); - - const { t } = useTranslation(); - - return ( - - - {t("govActions.filterTitle")} - - {GOVERNANCE_ACTIONS_FILTERS.map((item) => { - return ( - - - } - label={ - - {item.label} - - } - /> - - ); - })} - - ); -}; diff --git a/govtool/frontend/src/components/molecules/GovernanceActionsSorting.tsx b/govtool/frontend/src/components/molecules/GovernanceActionsSorting.tsx deleted file mode 100644 index b75cf3e34..000000000 --- a/govtool/frontend/src/components/molecules/GovernanceActionsSorting.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import { Dispatch, SetStateAction } from "react"; -import { - Box, - FormControl, - FormControlLabel, - Radio, - RadioGroup, - Typography, -} from "@mui/material"; - -import { GOVERNANCE_ACTIONS_SORTING } from "@consts"; -import { useTranslation } from "@hooks"; - -interface Props { - chosenSorting: string; - setChosenSorting: Dispatch>; -} - -export const GovernanceActionsSorting = ({ - chosenSorting, - setChosenSorting, -}: Props) => { - const { t } = useTranslation(); - - return ( - - - - - {t("sortBy")} - - setChosenSorting("")}> - - {t("clear")} - - - - { - setChosenSorting(e.target.value); - }} - > - {GOVERNANCE_ACTIONS_SORTING.map((item) => { - return ( - - } - label={item.label} - /> - ); - })} - - - - ); -}; diff --git a/govtool/frontend/src/components/molecules/GovernanceVotedOnCard.tsx b/govtool/frontend/src/components/molecules/GovernanceVotedOnCard.tsx index f5181769d..bdc2e499d 100644 --- a/govtool/frontend/src/components/molecules/GovernanceVotedOnCard.tsx +++ b/govtool/frontend/src/components/molecules/GovernanceVotedOnCard.tsx @@ -1,279 +1,152 @@ import { useNavigate } from "react-router-dom"; import { Box } from "@mui/material"; -import CheckIcon from "@mui/icons-material/Check"; -import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined"; -import { Button, VotePill, Typography } from "@atoms"; +import { Button } from "@atoms"; import { PATHS } from "@consts"; import { useScreenDimension, useTranslation } from "@hooks"; -import { VotedProposal } from "@models"; -import { theme } from "@/theme"; +import { VotedProposalToDisplay } from "@models"; import { formatDisplayDate, getFullGovActionId, getProposalTypeLabel, - getShortenedGovActionId, - openInNewTab, + getProposalTypeNoEmptySpaces, } from "@utils"; -import { Tooltip } from "@atoms"; +import { + GovernanceActionCardElement, + GovernanceActionCardHeader, + GovernanceActionCardMyVote, + GovernanceActionCardStatePill, + GovernanceActionsDatesBox, +} from "@molecules"; -interface Props { - votedProposal: VotedProposal; - searchPhrase?: string; +type Props = { + votedProposal: VotedProposalToDisplay; inProgress?: boolean; -} +}; export const GovernanceVotedOnCard = ({ votedProposal, inProgress }: Props) => { const navigate = useNavigate(); const { proposal, vote } = votedProposal; const { - palette: { lightBlue }, - } = theme; - const { isMobile } = useScreenDimension(); - const { t } = useTranslation(); + about, + createdDate, + createdEpochNo, + expiryDate, + expiryEpochNo, + index, + title, + txHash, + type, + isDataMissing, + } = proposal; - const proposalTypeNoEmptySpaces = getProposalTypeLabel(proposal.type).replace( - / /g, - "" - ); + const { isMobile, screenWidth } = useScreenDimension(); + const { t } = useTranslation(); return ( + - - {inProgress ? ( - t("inProgress") - ) : ( - <> - - {t("govActions.voteSubmitted")} - - )} - + + + + + + - - - {t("govActions.governanceActionType")} - - - - - {getProposalTypeLabel(proposal.type)} - - - - - - - {t("govActions.governanceActionId")} - - - - - {getShortenedGovActionId(proposal.txHash, proposal.index)} - - - - - - - {t("govActions.myVote")} - - - - - - - - - - {proposal.createdDate ? ( - - - {t("govActions.submissionDate")} - - - {formatDisplayDate(proposal.createdDate)} - - - - - - ) : null} - {proposal.expiryDate ? ( - - - {t("govActions.expiryDate")} - - - {formatDisplayDate(proposal.expiryDate)} - - - - - - ) : null} - diff --git a/govtool/frontend/src/components/molecules/LinkWithIcon.tsx b/govtool/frontend/src/components/molecules/LinkWithIcon.tsx index 64960531d..ecfa97b16 100644 --- a/govtool/frontend/src/components/molecules/LinkWithIcon.tsx +++ b/govtool/frontend/src/components/molecules/LinkWithIcon.tsx @@ -10,28 +10,38 @@ export const LinkWithIcon = ({ onClick, icon, sx, -}: LinkWithIconProps) => { - return ( - ( + + {icon || } + - {icon ? icon : } - - {label} - - - ); -}; + {label} + + +); diff --git a/govtool/frontend/src/components/molecules/OrderActionsChip.tsx b/govtool/frontend/src/components/molecules/OrderActionsChip.tsx index ecf6093f3..006133653 100644 --- a/govtool/frontend/src/components/molecules/OrderActionsChip.tsx +++ b/govtool/frontend/src/components/molecules/OrderActionsChip.tsx @@ -1,23 +1,30 @@ import { Dispatch, SetStateAction } from "react"; -import { Box, Typography } from "@mui/material"; +import { Box } from "@mui/material"; +import { useScreenDimension, useTranslation } from "@hooks"; +import { Typography } from "@atoms"; import { ICONS } from "@consts"; import { theme } from "@/theme"; -interface Props { +type Props = { filtersOpen?: boolean; setFiltersOpen?: Dispatch>; chosenFiltersLength?: number; sortOpen: boolean; setSortOpen: Dispatch>; sortingActive: boolean; + children?: React.ReactNode; isFiltering?: boolean; -} +}; export const OrderActionsChip = (props: Props) => { + const { t } = useTranslation(); + const { isMobile } = useScreenDimension(); + const { palette: { secondary }, } = theme; + const { filtersOpen, setFiltersOpen = () => {}, @@ -26,33 +33,69 @@ export const OrderActionsChip = (props: Props) => { setSortOpen, sortingActive, isFiltering = true, + children, } = props; return ( - + {isFiltering && ( - + { + setSortOpen(false); + if (isFiltering) { + setFiltersOpen(!filtersOpen); + } + }} + data-testid="filters-button" + > filter { - setSortOpen(false); - if (isFiltering) { - setFiltersOpen(!filtersOpen); - } - }} src={filtersOpen ? ICONS.filterWhiteIcon : ICONS.filterIcon} style={{ - background: filtersOpen ? secondary.main : "transparent", borderRadius: "100%", - cursor: "pointer", - padding: "14px", + marginRight: "8px", overflow: "visible", height: 20, width: 20, objectFit: "contain", + ...(isMobile && { + background: filtersOpen ? secondary.main : "transparent", + padding: "14px", + marginRight: "0", + }), }} /> + {!isMobile && ( + + {t("filter")} + + )} {!filtersOpen && chosenFiltersLength > 0 && ( { height: "16px", justifyContent: "center", position: "absolute", - right: "0", + right: "-3px", top: "0", width: "16px", }} @@ -77,27 +120,54 @@ export const OrderActionsChip = (props: Props) => { )} )} - + { + if (isFiltering) { + setFiltersOpen(false); + } + setSortOpen(!sortOpen); + }} + data-testid="sort-button" + > sort { - if (isFiltering) { - setFiltersOpen(false); - } - setSortOpen(!sortOpen); - }} src={sortOpen ? ICONS.sortWhiteIcon : ICONS.sortIcon} style={{ - background: sortOpen ? secondary.main : "transparent", borderRadius: "100%", - cursor: "pointer", - padding: "14px", - height: 24, - width: 24, + marginRight: "8px", + height: 20, + width: 20, objectFit: "contain", + ...(isMobile && { + background: sortOpen ? secondary.main : "transparent", + padding: "14px", + marginRight: "0", + }), }} /> + {!isMobile && ( + + {t("sort")} + + )} {!sortOpen && sortingActive && ( { height: "16px", justifyContent: "center", position: "absolute", - right: "0", + right: "-3px", top: "0", width: "16px", }} @@ -119,6 +189,7 @@ export const OrderActionsChip = (props: Props) => { )} + {children} ); }; diff --git a/govtool/frontend/src/components/molecules/Share.tsx b/govtool/frontend/src/components/molecules/Share.tsx new file mode 100644 index 000000000..a57e7763d --- /dev/null +++ b/govtool/frontend/src/components/molecules/Share.tsx @@ -0,0 +1,97 @@ +import { useState } from "react"; +import { Box, Popover } from "@mui/material"; + +import { Typography } from "@atoms"; +import { ICONS } from "@consts"; +import { useSnackbar } from "@context"; +import { useTranslation } from "@hooks"; + +export const Share = ({ link }: { link: string }) => { + const { addSuccessAlert } = useSnackbar(); + const { t } = useTranslation(); + const [anchorEl, setAnchorEl] = useState(null); + + const handleClick = (e: React.MouseEvent) => { + setAnchorEl(e.currentTarget); + }; + + const handleClose = () => { + setAnchorEl(null); + }; + + const onCopy = (e: React.MouseEvent) => { + navigator.clipboard.writeText(link); + addSuccessAlert(t("alerts.copiedToClipboard")); + e.stopPropagation(); + }; + + const open = Boolean(anchorEl); + const id = open ? "simple-popover" : undefined; + + return ( + <> + + share icon + + + + {t("share")} + + link + + {t("clickToCopyLink")} + + + + ); +}; diff --git a/govtool/frontend/src/components/molecules/SliderArrow.tsx b/govtool/frontend/src/components/molecules/SliderArrow.tsx new file mode 100644 index 000000000..b4b079d48 --- /dev/null +++ b/govtool/frontend/src/components/molecules/SliderArrow.tsx @@ -0,0 +1,45 @@ +import { Box } from "@mui/material"; +import ChevronRightIcon from "@mui/icons-material/ChevronRight"; + +import { theme } from "@/theme"; + +type SliderArrowProps = { + disabled: boolean; + onClick: (e: React.MouseEvent) => void; + left?: boolean; +}; + +export const SliderArrow = ({ disabled, onClick, left }: SliderArrowProps) => { + const { + palette: { primaryBlue, arcticWhite, lightBlue }, + } = theme; + + return ( + + + + ); +}; diff --git a/govtool/frontend/src/components/molecules/SliderArrows.tsx b/govtool/frontend/src/components/molecules/SliderArrows.tsx new file mode 100644 index 000000000..6d5f9e7a6 --- /dev/null +++ b/govtool/frontend/src/components/molecules/SliderArrows.tsx @@ -0,0 +1,46 @@ +import { KeenSliderHooks, KeenSliderInstance } from "keen-slider/react"; +import { SliderArrow } from "@molecules"; +import { Box } from "@mui/material"; + +type SliderArrowsProps = { + currentSlide: number; + instanceRef: React.MutableRefObject | null>; + itemsPerView: number; +}; + +export const SliderArrows = ({ + currentSlide, + instanceRef, + itemsPerView, +}: SliderArrowsProps) => + instanceRef.current && ( + + ) => { + e.stopPropagation(); + instanceRef.current?.prev(); + }} + disabled={currentSlide === 0} + /> + ) => { + e.stopPropagation(); + instanceRef.current?.next(); + }} + disabled={ + currentSlide + itemsPerView >= + instanceRef.current.track.details.slides.length + } + /> + + ); diff --git a/govtool/frontend/src/components/molecules/SoleVoterAction.tsx b/govtool/frontend/src/components/molecules/SoleVoterAction.tsx new file mode 100644 index 000000000..c58e60485 --- /dev/null +++ b/govtool/frontend/src/components/molecules/SoleVoterAction.tsx @@ -0,0 +1,55 @@ +import { Box } from "@mui/material"; +import ArrowForwardIosIcon from "@mui/icons-material/ArrowForwardIos"; + +import { Typography } from "@atoms"; +import { gray } from "@consts"; +import { useTranslation } from "@hooks"; + +import { Card } from "./Card"; +import { SoleVoterActionProps } from "./types"; + +export const SoleVoterAction = ({ + dRepId, + onClickArrow, + sx, +}: SoleVoterActionProps) => { + const { t } = useTranslation(); + + return ( + theme.palette.neutralWhite, + borderColor: gray.c100, + display: "flex", + justifyContent: "space-between", + px: 1.5, + py: 1, + ...sx, + }} + > + + + {t("dashboard.cards.drepName")} + + + {dRepId} + + + + + ); +}; diff --git a/govtool/frontend/src/components/molecules/Step.tsx b/govtool/frontend/src/components/molecules/Step.tsx index 80af496f7..7b8e1a0b4 100644 --- a/govtool/frontend/src/components/molecules/Step.tsx +++ b/govtool/frontend/src/components/molecules/Step.tsx @@ -7,6 +7,7 @@ import { StepProps } from "./types"; export const Step = ({ component, + componentsLayoutStyles, label, layoutStyles, stepNumber, @@ -46,9 +47,10 @@ export const Step = ({ flex: 1, flexDirection: "column", ml: 3, + ...componentsLayoutStyles, }} > - + {label} {component} diff --git a/govtool/frontend/src/components/molecules/VoteActionForm.tsx b/govtool/frontend/src/components/molecules/VoteActionForm.tsx index 920021f24..b979ec247 100644 --- a/govtool/frontend/src/components/molecules/VoteActionForm.tsx +++ b/govtool/frontend/src/components/molecules/VoteActionForm.tsx @@ -1,64 +1,98 @@ -import { useState, useEffect, useMemo, useCallback } from "react"; +import { useState, useEffect, useMemo, Dispatch, SetStateAction } from "react"; import { useLocation } from "react-router-dom"; -import { Box, Link } from "@mui/material"; +import { Box } from "@mui/material"; +import { Trans } from "react-i18next"; -import { Button, LoadingButton, Radio, Spacer, Typography } from "@atoms"; -import { ICONS } from "@consts"; -import { useCardano, useModal } from "@context"; -import { useScreenDimension, useVoteActionForm, useTranslation } from "@hooks"; -import { openInNewTab } from "@utils"; +import { Button, LoadingButton, Radio, Typography } from "@atoms"; +import { orange } from "@consts"; +import { useModal } from "@context"; +import { + useScreenDimension, + useVoteActionForm, + useTranslation, + useGetVoterInfo, + useGetVoteContextTextFromFile, +} from "@hooks"; +import { formatDisplayDate } from "@utils"; -import { ControlledField } from "../organisms"; +type VoteActionFormProps = { + setIsVoteSubmitted: Dispatch>; + expiryDate: string; + expiryEpochNo: number; + voteFromEP?: string; + voteUrlFromEP?: string; + voteDateFromEP?: string; + voteEpochNoFromEP?: number; + yesVotes: number; + noVotes: number; + abstainVotes: number; + isInProgress?: boolean; +}; export const VoteActionForm = ({ + setIsVoteSubmitted, + expiryDate, + expiryEpochNo, voteFromEP, + voteUrlFromEP, + voteDateFromEP, + voteEpochNoFromEP, yesVotes, noVotes, abstainVotes, -}: { - voteFromEP?: string; - yesVotes: number; - noVotes: number; - abstainVotes: number; -}) => { + isInProgress, +}: VoteActionFormProps) => { + const [voteContextHash, setVoteContextHash] = useState(); + const [voteContextUrl, setVoteContextUrl] = useState(); + const [showWholeVoteContext, setShowWholeVoteContext] = + useState(false); + + const { voter } = useGetVoterInfo(); + const { voteContextText } = useGetVoteContextTextFromFile(voteContextUrl); + const { state } = useLocation(); - const [isContext, setIsContext] = useState(false); const { isMobile, screenWidth } = useScreenDimension(); const { openModal } = useModal(); - const { voter } = useCardano(); const { t } = useTranslation(); + const voteDate = state ? state.voteDate : voteDateFromEP; + const voteEpochNo = state ? state.voteEpochNo : voteEpochNoFromEP; + const { areFormErrors, - clearErrors, confirmVote, - control, - errors, isDirty, isVoteLoading, registerInput, setValue, vote, - } = useVoteActionForm(); + } = useVoteActionForm(voteContextHash, voteContextUrl); + + const setVoteContextData = (url: string, hash: string) => { + setVoteContextUrl(url); + setVoteContextHash(hash); + }; useEffect(() => { if (state && state.vote) { setValue("vote", state.vote); + setIsVoteSubmitted(true); } else if (voteFromEP) { setValue("vote", voteFromEP); + setIsVoteSubmitted(true); } }, [state, voteFromEP, setValue]); useEffect(() => { - clearErrors(); - }, [isContext]); - - const handleContext = useCallback(() => { - setIsContext((prev) => !prev); - }, []); + if (state && state.voteUrl) { + setVoteContextUrl(state.voteUrl); + } else if (voteUrlFromEP) { + setVoteContextUrl(voteUrlFromEP); + } + }, [voteUrlFromEP, state]); - const renderCancelButton = useMemo(() => { - return ( + const renderCancelButton = useMemo( + () => ( - ); - }, [state]); + ), + [state], + ); - const renderChangeVoteButton = useMemo(() => { - return ( + const renderChangeVoteButton = useMemo( + () => ( {t("govActions.changeVote")} - ); - }, [confirmVote, areFormErrors, vote, isVoteLoading]); + ), + [confirmVote, areFormErrors, vote, isVoteLoading], + ); return ( - - - - {t("govActions.chooseHowToVote")} - - - - - - - - - - + + + {voteDate ? ( + <> + + ]} + /> + + + {t("govActions.castVoteDeadline", { + date: expiryDate, + epoch: expiryEpochNo, + })} + + + ) : ( + + {t("govActions.chooseHowToVote")} + + )} + + + + {(voter?.isRegisteredAsDRep || voter?.isRegisteredAsSoleVoter) && ( )} - -

    + + {voteContextText + ? t("govActions.contextAboutYourVote") + : t("govActions.youCanProvideContext")} + + {voteContextText && ( + - {t("govActions.provideContext")}{" "} - - {t("govActions.optional")} - arrow - -

    -
    - {isContext && ( - - - - - - - openInNewTab( - "https://docs.sanchogov.tools/faqs/how-to-create-a-metadata-anchor" - ) - } - mb={isMobile ? 2 : 8} - sx={{ cursor: "pointer" }} - textAlign="center" - visibility={!isContext ? "hidden" : "visible"} + + {voteContextText} + + )} +
    { - const { - palette: { lightBlue }, - } = theme; - const { isMobile } = useScreenDimension(); const { t } = useTranslation(); return ( @@ -31,72 +26,66 @@ export const VotesSubmitted = ({ yesVotes, noVotes, abstainVotes }: Props) => { src={IMAGES.govActionListImage} width="64px" height="64px" - style={{ marginBottom: "10px" }} + style={{ marginBottom: "24px" }} /> - - {t("govActions.voteSubmitted")} - - - {t("govActions.forGovAction")} - - {t("govActions.votesSubmittedOnChain")} + {t("govActions.voteSubmitted")} - {t("govActions.votes")} + {t("govActions.forGovAction")} + + + {t("govActions.votesSubmittedOnChain")} - + ₳ {correctAdaFormat(yesVotes)} - + ₳ {correctAdaFormat(abstainVotes)} - + ₳ {correctAdaFormat(noVotes)} diff --git a/govtool/frontend/src/components/molecules/WalletInfoCard.tsx b/govtool/frontend/src/components/molecules/WalletInfoCard.tsx index d1ab3ef11..b9d3a482c 100644 --- a/govtool/frontend/src/components/molecules/WalletInfoCard.tsx +++ b/govtool/frontend/src/components/molecules/WalletInfoCard.tsx @@ -1,9 +1,10 @@ import { useNavigate } from "react-router-dom"; import { Box, Button, Typography } from "@mui/material"; -import { PATHS } from "@consts"; +import { PATHS, gray } from "@consts"; import { useCardano } from "@context"; import { useTranslation } from "@hooks"; +import { Card } from "./Card"; export const WalletInfoCard = () => { const { address, disconnectWallet } = useCardano(); @@ -18,17 +19,8 @@ export const WalletInfoCard = () => { return ( address && ( - - + + {t("wallet.connectedWallet")} @@ -45,14 +37,14 @@ export const WalletInfoCard = () => { {address} - + ) ); }; diff --git a/govtool/frontend/src/components/molecules/WalletOption.tsx b/govtool/frontend/src/components/molecules/WalletOption.tsx index a1a14dd69..1b60e6dc7 100644 --- a/govtool/frontend/src/components/molecules/WalletOption.tsx +++ b/govtool/frontend/src/components/molecules/WalletOption.tsx @@ -23,15 +23,15 @@ export const WalletOptionButton: FC = ({ ...props }) => { const { dataTestId, icon, label, name, cip95Available } = props; - const enableByWalletName = useCallback(async() => { - if(isEnableLoading) return; + const enableByWalletName = useCallback(async () => { + if (isEnableLoading) return; const result = await enable(name); if (result?.stakeKey) { navigate(PATHS.dashboard); return; } navigate(PATHS.stakeKeys); - }, [enable, isEnableLoading]) + }, [enable, isEnableLoading]); return ( void; icon?: JSX.Element; sx?: SxProps; + cutWithEllipsis?: boolean; }; export type StepProps = { - component: JSX.Element; label: string; - layoutStyles?: SxProps; stepNumber: number | string; + component?: React.ReactNode; + componentsLayoutStyles?: SxProps; + layoutStyles?: SxProps; +}; + +export type SoleVoterActionProps = { + dRepId: string; + onClickArrow: () => void; + sx?: SxProps; +}; + +export type EmptyStateGovernanceActionsCategoryProps = { + category?: string; + isSearch?: boolean; }; diff --git a/govtool/frontend/src/components/organisms/BgCard.tsx b/govtool/frontend/src/components/organisms/BgCard.tsx index c8b7a0d48..748674365 100644 --- a/govtool/frontend/src/components/organisms/BgCard.tsx +++ b/govtool/frontend/src/components/organisms/BgCard.tsx @@ -28,11 +28,11 @@ export const BgCard = ({ const navigateToDashboard = useCallback( () => navigate(PATHS.dashboard), - [navigate] + [navigate], ); - const renderBackButton = useMemo(() => { - return ( + const renderBackButton = useMemo( + () => ( - ); - }, [isMobile]); + ), + [isMobile], + ); - const renderContinueButton = useMemo(() => { - return ( + const renderContinueButton = useMemo( + () => ( {actionButtonLabel} - ); - }, [ - actionButtonLabel, - isActionButtonDisabled, - isLoadingActionButton, - isMobile, - ]); + ), + [ + actionButtonLabel, + isActionButtonDisabled, + isLoadingActionButton, + isMobile, + ], + ); return ( { const { disconnectWallet, stakeKeys, setStakeKey } = useCardano(); @@ -20,10 +20,10 @@ export const ChooseStakeKeyPanel = () => { palette: { boxShadow2 }, } = theme; - const renderCancelButton = useMemo(() => { - return ( + const renderCancelButton = useMemo( + () => ( - ); - }, [isMobile]); + ), + [isMobile], + ); - const renderSelectButton = useMemo(() => { - return ( + const renderSelectButton = useMemo( + () => ( - ); - }, [isMobile, chosenKey, setStakeKey]); + ), + [isMobile, chosenKey, setStakeKey], + ); return ( { width={isMobile ? "100%" : "auto"} > { py={isMobile ? 4 : 6} > - + {t("wallet.pickStakeKey")} - + {t("wallet.pickStakeKey")} - {stakeKeys.map((k) => { - return ( - - - - ); - })} + {stakeKeys.map((k) => ( + + + + ))} {isMobile ? renderSelectButton : renderCancelButton} diff --git a/govtool/frontend/src/components/organisms/ChooseWalletModal.tsx b/govtool/frontend/src/components/organisms/ChooseWalletModal.tsx index 051bc850d..e66f28ab3 100644 --- a/govtool/frontend/src/components/organisms/ChooseWalletModal.tsx +++ b/govtool/frontend/src/components/organisms/ChooseWalletModal.tsx @@ -7,7 +7,7 @@ import { WalletOptionButton } from "@molecules"; import { openInNewTab } from "@utils"; import { useTranslation } from "@hooks"; -export function ChooseWalletModal() { +export const ChooseWalletModal = () => { const { t } = useTranslation(); const walletOptions: WalletOption[] = useMemo(() => { @@ -19,11 +19,11 @@ export function ChooseWalletModal() { if (icon && name && supportedExtensions) { // Check if the name already exists in resultWallets const isNameDuplicate = resultWallets.some( - (wallet) => wallet.label === name + (wallet) => wallet.label === name, ); // Check if the supportedExtensions array contains an entry with cip === 95 const isCip95Available = Boolean( - supportedExtensions?.find((i) => i.cip === 95) + supportedExtensions?.find((i) => i.cip === 95), ); // If the name is not a duplicate and cip === 95 is available, add it to resultWallets if (!isNameDuplicate && isCip95Available) { @@ -73,18 +73,16 @@ export function ChooseWalletModal() { {t("wallet.noWalletsToConnect")} ) : ( - walletOptions.map(({ icon, label, name, cip95Available }) => { - return ( - - ); - }) + walletOptions.map(({ icon, label, name, cip95Available }) => ( + + )) )}
    openInNewTab( - "https://docs.sanchogov.tools/how-to-use-the-govtool/getting-started/get-a-compatible-wallet" + "https://docs.sanchogov.tools/how-to-use-the-govtool/getting-started/get-a-compatible-wallet", ) } sx={{ cursor: "pointer" }} @@ -113,4 +111,4 @@ export function ChooseWalletModal() { ); -} +}; diff --git a/govtool/frontend/src/components/organisms/ControlledField/Checkbox.tsx b/govtool/frontend/src/components/organisms/ControlledField/Checkbox.tsx index cacda8f77..f04ec73dc 100644 --- a/govtool/frontend/src/components/organisms/ControlledField/Checkbox.tsx +++ b/govtool/frontend/src/components/organisms/ControlledField/Checkbox.tsx @@ -25,7 +25,7 @@ export const Checkbox = ({ {...props} /> ), - [errorMessage, props] + [errorMessage, props], ); return ( diff --git a/govtool/frontend/src/components/organisms/ControlledField/Input.tsx b/govtool/frontend/src/components/organisms/ControlledField/Input.tsx index d9a2e2f34..20b594425 100644 --- a/govtool/frontend/src/components/organisms/ControlledField/Input.tsx +++ b/govtool/frontend/src/components/organisms/ControlledField/Input.tsx @@ -18,7 +18,7 @@ export const Input = forwardRef( ref={ref} /> ), - [errorMessage, props] + [errorMessage, props], ); return ( @@ -29,5 +29,5 @@ export const Input = forwardRef( render={renderInput} /> ); - } + }, ); diff --git a/govtool/frontend/src/components/organisms/ControlledField/TextArea.tsx b/govtool/frontend/src/components/organisms/ControlledField/TextArea.tsx index 163b6562d..9930c3a21 100644 --- a/govtool/frontend/src/components/organisms/ControlledField/TextArea.tsx +++ b/govtool/frontend/src/components/organisms/ControlledField/TextArea.tsx @@ -18,7 +18,7 @@ export const TextArea = ({ ({ field }: RenderInputProps) => ( ), - [errorMessage, props] + [errorMessage, props], ); return ( diff --git a/govtool/frontend/src/components/organisms/ControlledField/index.tsx b/govtool/frontend/src/components/organisms/ControlledField/index.tsx index e7a50f7ed..9962deec7 100644 --- a/govtool/frontend/src/components/organisms/ControlledField/index.tsx +++ b/govtool/frontend/src/components/organisms/ControlledField/index.tsx @@ -10,9 +10,9 @@ type ControlledFieldComposition = React.FC & { TextArea: typeof TextArea; }; -const ControlledField: ControlledFieldComposition = ({ children }) => { - return <>{children}; -}; +const ControlledField: ControlledFieldComposition = ({ children }) => ( + <>{children} +); ControlledField.Checkbox = Checkbox; ControlledField.Input = Input; diff --git a/govtool/frontend/src/components/organisms/ControlledField/types.ts b/govtool/frontend/src/components/organisms/ControlledField/types.ts index 6bf85727d..c20302a8f 100644 --- a/govtool/frontend/src/components/organisms/ControlledField/types.ts +++ b/govtool/frontend/src/components/organisms/ControlledField/types.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import { CheckboxFieldProps, InputFieldProps, diff --git a/govtool/frontend/src/components/organisms/CreateGovernanceActionSteps/ChooseGovernanceActionType.tsx b/govtool/frontend/src/components/organisms/CreateGovernanceActionSteps/ChooseGovernanceActionType.tsx index e90606635..49dd7af0d 100644 --- a/govtool/frontend/src/components/organisms/CreateGovernanceActionSteps/ChooseGovernanceActionType.tsx +++ b/govtool/frontend/src/components/organisms/CreateGovernanceActionSteps/ChooseGovernanceActionType.tsx @@ -30,6 +30,10 @@ export const ChooseGovernanceActionType = ({ setStep(1); }; + const onChangeType = (value: string) => { + setValue("governance_action_type", value as GovernanceActionType); + }; + // TODO: Add tooltips when they will be available const renderGovernanceActionTypes = () => Object.keys(GovernanceActionType).map( @@ -46,13 +50,9 @@ export const ChooseGovernanceActionType = ({ {index + 1 < governanceActionTypes.length ? : null} ); - } + }, ); - const onChangeType = (value: string) => { - setValue("governance_action_type", value as GovernanceActionType); - }; - return ( !watch(field) + (field) => !watch(field as unknown as Parameters[0]), ) || isError; const onClickContinue = () => { @@ -53,52 +53,39 @@ export const CreateGovernanceActionForm = ({ setStep(2); }; - const renderGovernanceActionField = () => { - return Object.entries(GOVERNANCE_ACTION_FIELDS[type!]).map( - ([key, field]) => { - const fieldProps = { - helpfulText: field.tipI18nKey ? t(field.tipI18nKey) : undefined, - key, - label: t(field.labelI18nKey), - layoutStyles: { mb: 3 }, - name: key, - placeholder: field.placeholderI18nKey - ? t(field.placeholderI18nKey) - : undefined, - rules: field.rules, - }; - - if (field.component === GovernanceActionField.Input) { - return ( - - ); - } - if (field.component === GovernanceActionField.TextArea) { - return ( - - ); - } + const renderGovernanceActionField = () => + Object.entries(GOVERNANCE_ACTION_FIELDS[type!]).map(([key, field]) => { + const fieldProps = { + helpfulText: field.tipI18nKey ? t(field.tipI18nKey) : undefined, + key, + label: t(field.labelI18nKey), + layoutStyles: { mb: 3 }, + name: key, + placeholder: field.placeholderI18nKey + ? t(field.placeholderI18nKey) + : undefined, + rules: field.rules, + }; + + if (field.component === GovernanceActionField.Input) { + return ( + + ); } - ); - }; + if (field.component === GovernanceActionField.TextArea) { + return ( + + ); + } + }); - const addLink = useCallback(() => { - append({ link: "" }); - }, [append]); + const addLink = useCallback(() => append({ link: "" }), [append]); - const removeLink = useCallback( - (index: number) => { - remove(index); - }, - [remove] - ); + const removeLink = useCallback((index: number) => remove(index), [remove]); - const renderLinks = useCallback(() => { - return links.map((field, index) => { - return ( + const renderLinks = useCallback( + () => + links.map((field, index) => ( - ); - }); - }, [links]); + )), + [links], + ); return ( >; @@ -41,12 +41,12 @@ export const ReviewCreatedGovernanceAction = ({ openInNewTab(link); }; - const renderReviewFields = () => { - return Object.entries(values) + const renderReviewFields = () => + Object.entries(values) .filter( ([key]) => !Object.keys(defaulCreateGovernanceActionValues).includes(key) || - key === "governance_action_type" + key === "governance_action_type", ) .map(([key, value]) => { const label = @@ -66,10 +66,9 @@ export const ReviewCreatedGovernanceAction = ({
    ); }); - }; const renderLinks = () => { - const links = values["links"]?.map((item) => item.link) ?? []; + const links = values.links?.map((item) => item.link) ?? []; const areLinks = links.some((item) => item); return areLinks ? ( @@ -82,16 +81,17 @@ export const ReviewCreatedGovernanceAction = ({ > {t("createGovernanceAction.supportingLinks")}
    - {links.map((link: string) => { - return link ? ( - } - label={link} - onClick={() => onClickLink(link)} - sx={{ mb: 1.75 }} - /> - ) : null; - })} + {links.map( + (link: string) => + link && ( + } + label={link} + onClick={() => onClickLink(link)} + sx={{ mb: 1.75 }} + /> + ), + )} ) : null; }; diff --git a/govtool/frontend/src/components/organisms/CreateGovernanceActionSteps/StorageInformation.tsx b/govtool/frontend/src/components/organisms/CreateGovernanceActionSteps/StorageInformation.tsx index 3dbd72485..015ae75d1 100644 --- a/govtool/frontend/src/components/organisms/CreateGovernanceActionSteps/StorageInformation.tsx +++ b/govtool/frontend/src/components/organisms/CreateGovernanceActionSteps/StorageInformation.tsx @@ -1,10 +1,14 @@ -import { Dispatch, SetStateAction, useCallback } from "react"; +import { Dispatch, SetStateAction, useEffect } from "react"; import { Box } from "@mui/material"; import OpenInNewIcon from "@mui/icons-material/OpenInNew"; import { Button, Spacer, Typography } from "@atoms"; import { ICONS } from "@consts"; -import { useCreateGovernanceActionForm, useTranslation } from "@hooks"; +import { + useCreateGovernanceActionForm, + useTranslation, + useScreenDimension, +} from "@hooks"; import { Step } from "@molecules"; import { BgCard, ControlledField } from "@organisms"; import { URL_REGEX, openInNewTab } from "@utils"; @@ -21,22 +25,25 @@ export const StorageInformation = ({ setStep }: StorageInformationProps) => { createGovernanceAction, getValues, watch, + generateMetadata, onClickDownloadJson, isLoading, } = useCreateGovernanceActionForm(setStep); + const { screenWidth } = useScreenDimension(); - // TODO: change on correct file name const fileName = getValues("governance_action_type"); // TODO: Change link to correct - const openGuideAboutStoringInformation = useCallback( - () => openInNewTab("https://sancho.network/"), - [] - ); + const openGuideAboutStoringInformation = () => + openInNewTab("https://sancho.network/"); + + const isActionButtonDisabled = !watch("storingURL") || !!errors.storingURL; - const isActionButtonDisabled = !watch("storingURL"); + const onClickBack = () => setStep(5); - const onClickBack = useCallback(() => setStep(5), []); + useEffect(() => { + generateMetadata(); + }, []); return ( { {t("createGovernanceAction.storingInformationTitle")} - + } + onClick={openGuideAboutStoringInformation} + size="extraLarge" + sx={{ alignSelf: "center", width: "fit-content" }} + variant="text" > + {t("createGovernanceAction.storingInformationStep2Link")} + + {t("createGovernanceAction.storingInformationDescription")} @@ -63,12 +83,21 @@ export const StorageInformation = ({ setStep }: StorageInformationProps) => { } + componentsLayoutStyles={{ + alignItems: screenWidth < 1024 ? undefined : "center", + flexDirection: screenWidth < 1024 ? "column" : "row", + }} label={t("createGovernanceAction.storingInformationStep1Label")} stepNumber={1} /> @@ -102,14 +131,15 @@ export const StorageInformation = ({ setStep }: StorageInformationProps) => { { const { isMobile } = useScreenDimension(); // TODO: change link when available - const openLink = () => { - openInNewTab("https://docs.sanchogov.tools"); - }; + const openLink = () => openInNewTab("https://docs.sanchogov.tools"); const isContinueDisabled = !watch("storeData"); - const onClickContinue = () => { - setStep(6); - }; + const onClickContinue = () => setStep(6); - const onClickBack = () => { - setStep(4); - }; + const onClickBack = () => setStep(4); return ( { - const { - buildDRepRetirementCert, - buildSignSubmitConwayCertTx, - delegateTo, - delegateTransaction, - dRepID, - dRepIDBech32, - govActionTransaction, - isDrepLoading, - isPendingTransaction, - registerTransaction, - soleVoterTransaction, - stakeKey, - voter, - } = useCardano(); - const navigate = useNavigate(); - const { currentDelegation, isCurrentDelegationLoading } = - useGetAdaHolderCurrentDelegationQuery(stakeKey); + const { dRepID, dRepIDBech32, pendingTransaction, stakeKey } = useCardano(); const { screenWidth } = useScreenDimension(); - const { openModal } = useModal(); - const [isRetirementLoading, setIsRetirementLoading] = - useState(false); - const { votingPower, powerIsLoading } = - useGetAdaHolderVotingPowerQuery(stakeKey); - const { t } = useTranslation(); - const retireAsDrep = useCallback(async () => { - try { - setIsRetirementLoading(true); - const isPendingTx = isPendingTransaction(); - if (isPendingTx) return; - const certBuilder = await buildDRepRetirementCert(); - const result = await buildSignSubmitConwayCertTx({ - certBuilder, - type: "registration", - registrationType: "retirement", - }); - if (result) - openModal({ - type: "statusModal", - state: { - status: "success", - title: t("modals.retirement.title"), - message: t("modals.retirement.message"), - link: `https://adanordic.com/latest_transactions`, - buttonText: t("modals.common.goToDashboard"), - dataTestId: "retirement-transaction-submitted-modal", - }, - }); - } catch (error: any) { - const errorMessage = error.info ? error.info : error; + const { currentDelegation } = useGetAdaHolderCurrentDelegationQuery(stakeKey); + const { votingPower } = useGetAdaHolderVotingPowerQuery(stakeKey); + const { voter } = useGetVoterInfo(); - openModal({ - type: "statusModal", - state: { - status: "warning", - message: errorMessage, - buttonText: t("modals.common.goToDashboard"), - title: t("modals.common.oops"), - dataTestId: "retirement-transaction-error-modal", - }, - }); - } finally { - setIsRetirementLoading(false); - } - }, [ - buildDRepRetirementCert, - buildSignSubmitConwayCertTx, - isPendingTransaction, - openModal, - ]); + if ( + currentDelegation === undefined || + votingPower === undefined || + voter === undefined + ) { + return ( + + + + ); + } - const delegationDescription = useMemo(() => { - const correctAdaRepresentation = correctAdaFormat(votingPower); - if (currentDelegation === dRepID) { - return ( - - ); - } else if (currentDelegation === "drep_always_no_confidence") { - return ( - - ); - } else if (currentDelegation === "drep_always_abstain") { - return ( - - ); - } else if (currentDelegation) { - return ( - - ); - } else { - return ( - - ); - } - }, [currentDelegation, dRepID, votingPower]); - - const delegationStatusTestForId = useMemo(() => { - if (currentDelegation === dRepID) { - return "myself"; - } else if (currentDelegation === "drep_always_no_confidence") { - return "no-confidence"; - } else if (currentDelegation === "drep_always_abstain") { - return "abstain"; - } else if (currentDelegation) { - return "dRep"; - } else { - return "not_delegated"; - } - }, [currentDelegation, dRepID, votingPower]); - - const progressDescription = useMemo(() => { - const correctAdaRepresentation = correctAdaFormat(votingPower); - if (delegateTo === dRepID) { - return ( - - ); - } - if (delegateTo === "no confidence") { - return ( - - ); - } - if (delegateTo === "abstain") { - return ( - - ); - } - if (delegateTo) { - return ( - - ); - } - }, [delegateTo, dRepID, votingPower]); - - const navigateTo = useCallback( - (path: string) => { - const isPendingTx = isPendingTransaction(); - if (isPendingTx) return; - navigate(path); - }, - [isPendingTransaction, navigate] - ); - - const onClickGovernanceActionCardActionButton = useCallback(() => { - if (govActionTransaction.transactionHash) { - navigate(PATHS.dashboardGovernanceActions); - return; - } - navigate(PATHS.createGovernanceAction); - }, [govActionTransaction.transactionHash, navigate]); - - const displayedDelegationId = useMemo(() => { - const restrictedNames = [ - dRepID, - "drep_always_abstain", - "drep_always_no_confidence", - "abstain", - "no confidence", - ]; - if (delegateTransaction?.transactionHash) { - if (!restrictedNames.includes(delegateTo)) { - return delegateTo.includes("drep") - ? delegateTo - : formHexToBech32(delegateTo); - } - return undefined; - } - if (!restrictedNames.includes(currentDelegation)) { - return formHexToBech32(currentDelegation); - } else { - return undefined; - } - }, [ - currentDelegation, - dRepID, - delegateTo, - delegateTransaction, - formHexToBech32, - ]); - - const registrationCardDescription = useMemo(() => { - if (registerTransaction.transactionHash) { - switch (registerTransaction.type) { - case "retirement": - return t("dashboard.registration.retirementInProgress"); - case "registration": - return t("dashboard.registration.registrationInProgress"); - default: - return t("dashboard.registration.metadataUpdateInProgress"); - } - } else if (voter?.isRegisteredAsDRep || voter?.wasRegisteredAsDRep) { - return t("dashboard.registration.holdersCanDelegate"); - } else { - return t("dashboard.registration.ifYouWant"); - } - }, [ - registerTransaction.transactionHash, - registerTransaction.type, - voter?.isRegisteredAsDRep, - voter?.wasRegisteredAsDRep, - ]); - - const soleVoterCardDescription = useMemo(() => { - if (soleVoterTransaction.transactionHash) { - switch (soleVoterTransaction.type) { - case "retirement": - return "dashboard.soleVoter.retirementInProgress"; - default: - return "dashboard.soleVoter.registrationInProgress"; - } - } else if (voter?.isRegisteredAsSoleVoter) { - return "dashboard.soleVoter.isRegisteredDescription"; - } else if (voter?.wasRegisteredAsSoleVoter) { - return "dashboard.soleVoter.wasRegisteredDescription"; - } else { - return "dashboard.soleVoter.registerDescription"; - } - }, [ - soleVoterTransaction.transactionHash, - soleVoterTransaction.type, - voter?.isRegisteredAsSoleVoter, - voter?.wasRegisteredAsSoleVoter, - ]); - - const registrationCardTitle = useMemo(() => { - if (registerTransaction?.transactionHash) { - switch (registerTransaction.type) { - case "retirement": - return t("dashboard.registration.dRepRetirement"); - case "registration": - return t("dashboard.registration.dRepRegistration"); - default: - return t("dashboard.registration.dRepUpdate"); - } - } else if (voter?.isRegisteredAsDRep) { - return t("dashboard.registration.youAreRegistered"); - } else if (voter?.wasRegisteredAsDRep) { - return t("dashboard.registration.registerAgain"); - } else { - return t("dashboard.registration.registerAsDRep"); - } - }, [ - registerTransaction?.transactionHash, - registerTransaction.type, - voter?.isRegisteredAsDRep, - voter?.wasRegisteredAsDRep, - ]); - - const soleVoterCardTitle = useMemo(() => { - if (soleVoterTransaction?.transactionHash) { - switch (soleVoterTransaction.type) { - case "retirement": - return t("dashboard.soleVoter.retirement"); - default: - return t("dashboard.soleVoter.registration"); - } - } else if (voter?.isRegisteredAsSoleVoter) { - return t("dashboard.soleVoter.youAreSoleVoterTitle"); - } else if (voter?.wasRegisteredAsSoleVoter) { - return t("dashboard.soleVoter.wasSoleVoterTitle"); - } else { - return t("dashboard.soleVoter.registerTitle"); - } - }, [ - soleVoterTransaction?.transactionHash, - soleVoterTransaction.type, - voter?.isRegisteredAsSoleVoter, - voter?.isRegisteredAsSoleVoter, - ]); - - return isDrepLoading ? ( - - - - ) : ( + return ( { rowGap: 3, }} > - {/* DELEGATION CARD */} - navigateTo(PATHS.delegateTodRep)} - firstButtonLabel={ - delegateTransaction?.transactionHash - ? "" - : currentDelegation - ? t("dashboard.delegation.changeDelegation") - : t("delegate") - } - firstButtonVariant={currentDelegation ? "outlined" : "contained"} - imageURL={IMAGES.govActionDelegateImage} - cardId={displayedDelegationId} - inProgress={!!delegateTransaction?.transactionHash} - cardTitle={t("dashboard.delegation.dRepDelegatedTo")} - secondButtonAction={ - delegateTransaction?.transactionHash - ? () => openInNewTab("https://adanordic.com/latest_transactions") - : () => - openInNewTab( - "https://docs.sanchogov.tools/faqs/ways-to-use-your-voting-power" - ) - } - secondButtonLabel={ - delegateTransaction?.transactionHash - ? t("seeTransaction") - : currentDelegation - ? "" - : t("learnMore") - } - title={ - delegateTransaction?.transactionHash ? ( - t("dashboard.delegation.votingPowerDelegation") - ) : currentDelegation ? ( - - ) : ( - t("dashboard.delegation.useYourVotingPower") - ) - } + - {/* DELEGATION CARD END*/} - {/* REGISTARTION AS DREP CARD */} - navigateTo(PATHS.registerAsdRep) - } - firstButtonIsLoading={isRetirementLoading} - firstButtonLabel={ - registerTransaction?.transactionHash - ? "" - : t( - `dashboard.registration.${ - voter?.isRegisteredAsDRep ? "retire" : "register" - }` - ) - } - inProgress={!!registerTransaction?.transactionHash} - imageURL={IMAGES.govActionRegisterImage} - secondButtonAction={ - registerTransaction?.transactionHash - ? () => openInNewTab("https://adanordic.com/latest_transactions") - : voter?.isRegisteredAsDRep - ? () => { - navigateTo(PATHS.updateMetadata); - } - : () => - openInNewTab( - "https://docs.sanchogov.tools/faqs/what-does-it-mean-to-register-as-a-drep" - ) - } - secondButtonLabel={ - registerTransaction?.transactionHash - ? t("seeTransaction") - : voter?.isRegisteredAsDRep - ? t("dashboard.registration.changeMetadata") - : t("learnMore") - } - cardId={ - voter?.isRegisteredAsDRep || voter?.wasRegisteredAsDRep - ? dRepIDBech32 - : "" - } - cardTitle={ - voter?.isRegisteredAsDRep || voter?.wasRegisteredAsDRep - ? t("myDRepId") - : "" - } - title={registrationCardTitle} - /> - {/* DREP CARD END*/} - {/* SOLE VOTER CARD */} - - } - firstButtonLabel={ - soleVoterTransaction?.transactionHash - ? "" - : t( - voter?.isRegisteredAsSoleVoter - ? "dashboard.soleVoter.retire" - : voter?.wasRegisteredAsSoleVoter - ? "dashboard.soleVoter.reRegister" - : "dashboard.soleVoter.register" - ) - } - firstButtonAction={() => - navigateTo( - voter?.isRegisteredAsSoleVoter - ? PATHS.retireAsSoleVoter - : PATHS.registerAsSoleVoter - ) - } - firstButtonVariant={ - voter?.isRegisteredAsSoleVoter ? "outlined" : "contained" - } - secondButtonLabel={t("learnMore")} - secondButtonAction={() => - openInNewTab( - "https://docs.sanchogov.tools/faqs/what-does-it-mean-to-register-as-a-drep" - ) - } - secondButtonVariant="outlined" - imageURL={IMAGES.soleVoterImage} + + - {/* REGISTARTION AS SOLE VOTER CARD END*/} - {/* GOV ACTIONS LIST CARD */} - navigate(PATHS.dashboardGovernanceActions)} - firstButtonLabel={t( - `dashboard.govActions.${ - voter?.isRegisteredAsDRep ? "reviewAndVote" : "view" - }` - )} - imageURL={IMAGES.govActionListImage} - title={t("dashboard.govActions.title")} + + - {/* GOV ACTIONS LIST CARD END*/} - {/* GOV ACTIONS LIST CARD */} - - openInNewTab( - "https://docs.sanchogov.tools/faqs/what-is-a-governance-action" - ) - } - secondButtonVariant="outlined" - imageURL={IMAGES.proposeGovActionImage} - title={t("dashboard.proposeGovernanceAction.title")} + + + + - {/* GOV ACTIONS LIST CARD END*/} ); }; diff --git a/govtool/frontend/src/components/organisms/DashboardCards/DRepDashboardCard.tsx b/govtool/frontend/src/components/organisms/DashboardCards/DRepDashboardCard.tsx new file mode 100644 index 000000000..04f2b8849 --- /dev/null +++ b/govtool/frontend/src/components/organisms/DashboardCards/DRepDashboardCard.tsx @@ -0,0 +1,149 @@ +import { useNavigate } from "react-router-dom"; +import { Trans } from "react-i18next"; + +import { IMAGES, PATHS } from "@consts"; +import { PendingTransaction } from "@context"; +import { useTranslation } from "@hooks"; +import { VoterInfo } from "@models"; +import { + CopyableInfo, + DashboardActionCard, + DashboardActionCardProps, +} from "@molecules"; +import { correctAdaFormat, openInNewTab } from "@utils"; + +type DRepDashboardCardProps = { + dRepIDBech32: string; + pendingTransaction: PendingTransaction; + voter: VoterInfo; +}; + +export const DRepDashboardCard = ({ + dRepIDBech32, + pendingTransaction, + voter, +}: DRepDashboardCardProps) => { + const navigate = useNavigate(); + const { t } = useTranslation(); + + const inProgress = !!( + pendingTransaction.registerAsDrep || + pendingTransaction.retireAsDrep || + pendingTransaction.updateMetaData + ); + + const learnMoreButton = { + children: t("learnMore"), + dataTestId: "register-learn-more-button", + onClick: () => + openInNewTab( + "https://docs.sanchogov.tools/faqs/what-does-it-mean-to-register-as-a-drep", + ), + }; + + const cardProps: Partial = (() => { + // transaction in progress + if (inProgress) { + return { + buttons: [learnMoreButton], + state: "inProgress", + ...(pendingTransaction.registerAsDrep && { + description: t("dashboard.cards.drep.registrationInProgress"), + title: t("dashboard.cards.drep.dRepRegistration"), + }), + ...(pendingTransaction.retireAsDrep && { + description: ( + + ), + title: t("dashboard.cards.drep.dRepRetirement"), + }), + ...(pendingTransaction.updateMetaData && { + description: t("dashboard.cards.drep.metadataUpdateInProgress"), + title: t("dashboard.cards.drep.dRepUpdate"), + }), + }; + } + + // currently registered + if (voter?.isRegisteredAsDRep) { + return { + buttons: [ + { + children: t("dashboard.cards.drep.viewDetails"), + dataTestId: "view-drep-details-button", + // TODO: change navigation to drep explorer + onClick: () => navigate("/"), + variant: "outlined", + sx: { backgroundColor: "arcticWhite" }, + }, + { + children: t("dashboard.cards.drep.retire"), + dataTestId: "retire-button", + onClick: () => navigate(PATHS.retireAsDrep), + variant: "text", + }, + ], + description: t("dashboard.cards.drep.registeredDescription"), + state: "active", + title: t("dashboard.cards.drep.registeredTitle"), + }; + } + + // common buttons for was registered or not registered + const wasRegisteredOrNotRegisteredButtons: DashboardActionCardProps["buttons"] = + [ + { + children: t( + voter.wasRegisteredAsDRep + ? "dashboard.cards.drep.reRegister" + : "dashboard.cards.drep.register", + ), + dataTestId: "register-button", + onClick: () => navigate(PATHS.registerAsdRep), + variant: "contained", + }, + learnMoreButton, + ]; + + // was registered + if (voter?.wasRegisteredAsDRep) { + return { + buttons: wasRegisteredOrNotRegisteredButtons, + description: ( + + ), + title: t("dashboard.cards.drep.notRegisteredWasRegisteredTitle"), + }; + } + + // not registered + return { + buttons: wasRegisteredOrNotRegisteredButtons, + description: t("dashboard.cards.drep.notRegisteredDescription"), + title: t("dashboard.cards.drep.notRegisteredTitle"), + }; + })(); + + return ( + + {voter?.isRegisteredAsDRep && !pendingTransaction?.retireAsDrep && ( + + )} + + ); +}; diff --git a/govtool/frontend/src/components/organisms/DashboardCards/DelegateDashboardCard.tsx b/govtool/frontend/src/components/organisms/DashboardCards/DelegateDashboardCard.tsx new file mode 100644 index 000000000..6f68f06a9 --- /dev/null +++ b/govtool/frontend/src/components/organisms/DashboardCards/DelegateDashboardCard.tsx @@ -0,0 +1,185 @@ +import { useNavigate } from "react-router-dom"; +import { Trans } from "react-i18next"; + +import { IMAGES, PATHS } from "@consts"; +import { useTranslation } from "@hooks"; +import { + CopyableInfo, + DashboardActionCard, + DashboardActionCardProps, +} from "@molecules"; +import { correctAdaFormat, formHexToBech32, openInNewTab } from "@utils"; +import { PendingTransaction } from "@/context/pendingTransaction"; + +type DelegateDashboardCardProps = { + currentDelegation: string; + delegateTx: PendingTransaction["delegate"]; + dRepID: string; + votingPower: number; +}; + +export const DelegateDashboardCard = ({ + currentDelegation, + delegateTx, + dRepID, + votingPower, +}: DelegateDashboardCardProps) => { + const navigate = useNavigate(); + const { t } = useTranslation(); + + const ada = correctAdaFormat(votingPower); + + const cardProps: Partial = (() => { + // transaction in progress + if (delegateTx) { + return { + buttons: [ + { + children: t("seeTransaction"), + dataTestId: "see-transaction-button", + onClick: () => + openInNewTab("https://adanordic.com/latest_transactions"), + }, + ], + description: getProgressDescription( + delegateTx?.resourceId, + dRepID, + ada + ), + state: "inProgress", + title: t("dashboard.delegation.votingPowerDelegation"), + }; + } + + // current delegation + if (currentDelegation) { + return { + buttons: [ + { + children: t("dashboard.delegation.changeDelegation"), + dataTestId: "change-dRep-button", + onClick: () => navigate(PATHS.delegateTodRep), + }, + ], + description: getDelegationDescription(currentDelegation, dRepID, ada), + state: "active", + title: ( + + ), + }; + } + + // no current delegation + return { + buttons: [ + { + children: t("delegate"), + dataTestId: "delegate-button", + onClick: () => navigate(PATHS.delegateTodRep), + variant: "contained", + }, + { + children: t("learnMore"), + dataTestId: "delegate-learn-more-button", + onClick: () => + openInNewTab( + "https://docs.sanchogov.tools/faqs/ways-to-use-your-voting-power" + ), + }, + ], + description: ( + + ), + title: t("dashboard.delegation.useYourVotingPower"), + }; + })(); + + const displayedDelegationId = getDisplayedDelegationId( + currentDelegation, + delegateTx?.resourceId, + dRepID + ); + + return ( + + {displayedDelegationId && ( + + )} + + ); +}; + +const getDelegationDescription = ( + currentDelegation: string, + dRepID: string, + ada: number +) => { + const key = + currentDelegation === dRepID + ? "dashboard.delegation.toYourself" + : currentDelegation === "drep_always_no_confidence" + ? "dashboard.delegation.voteNo" + : currentDelegation === "drep_always_abstain" + ? "dashboard.delegation.voteAbstain" + : currentDelegation + ? "dashboard.delegation.toDRep" + : undefined; + return ; +}; + +const getProgressDescription = ( + delegateTo: string, + dRepID: string, + ada: number +) => { + const key = (() => { + if (!delegateTo) return undefined; + switch (delegateTo) { + case dRepID: + return "dashboard.delegation.inProgress.toYourself"; + case "no confidence": + return "dashboard.delegation.inProgress.voteNo"; + case "abstain": + return "dashboard.delegation.inProgress.voteAbstain"; + default: + return "dashboard.delegation.inProgress.toDRep"; + } + })(); + return ; +}; + +const getDisplayedDelegationId = ( + currentDelegation: string, + delegateTo: string | undefined, + dRepID: string +) => { + const restrictedNames = [ + dRepID, + "drep_always_abstain", + "drep_always_no_confidence", + "abstain", + "no confidence", + ]; + if (delegateTo) { + if (!restrictedNames.includes(delegateTo)) { + return delegateTo.includes("drep") + ? delegateTo + : formHexToBech32(delegateTo); + } + return undefined; + } + if (!restrictedNames.includes(currentDelegation)) { + return formHexToBech32(currentDelegation); + } + return undefined; +}; diff --git a/govtool/frontend/src/components/organisms/DashboardCards/ListGovActionsDashboardCard.tsx b/govtool/frontend/src/components/organisms/DashboardCards/ListGovActionsDashboardCard.tsx new file mode 100644 index 000000000..a32209292 --- /dev/null +++ b/govtool/frontend/src/components/organisms/DashboardCards/ListGovActionsDashboardCard.tsx @@ -0,0 +1,33 @@ +import { useNavigate } from "react-router-dom"; + +import { IMAGES, PATHS } from "@consts"; +import { useTranslation } from "@hooks"; +import { DashboardActionCard } from "@molecules"; +import { openInNewTab } from "@utils"; + +export const ListGovActionsDashboardCards = () => { + const navigate = useNavigate(); + const { t } = useTranslation(); + + return ( + navigate(PATHS.dashboardGovernanceActions), + variant: "contained", + }, + { + children: t("learnMore"), + dataTestId: "learn-more-governance-actions-button", + onClick: () => openInNewTab("https://sancho.network/actions"), + variant: "outlined", + }, + ]} + description={t("dashboard.cards.govActions.description")} + imageURL={IMAGES.govActionListImage} + title={t("dashboard.cards.govActions.title")} + /> + ); +}; diff --git a/govtool/frontend/src/components/organisms/DashboardCards/ProposeGovActionDashboardCard.tsx b/govtool/frontend/src/components/organisms/DashboardCards/ProposeGovActionDashboardCard.tsx new file mode 100644 index 000000000..295e3fc5c --- /dev/null +++ b/govtool/frontend/src/components/organisms/DashboardCards/ProposeGovActionDashboardCard.tsx @@ -0,0 +1,59 @@ +import { useNavigate } from "react-router-dom"; + +import { IMAGES, PATHS } from "@consts"; +import { PendingTransaction } from "@context"; +import { useTranslation } from "@hooks"; +import { DashboardActionCard } from "@molecules"; +import { openInNewTab } from "@utils"; + +type ProposeGovActionDashboardCardProps = { + createGovActionTx: PendingTransaction["createGovAction"]; +}; + +export const ProposeGovActionDashboardCard = ({ + createGovActionTx, +}: ProposeGovActionDashboardCardProps) => { + const navigate = useNavigate(); + const { t } = useTranslation(); + + return ( + navigate(PATHS.dashboardGovernanceActions), + variant: "contained", + } as const, + ] + : // default + [ + { + children: t("dashboard.cards.proposeGovernanceAction.propose"), + dataTestId: "propose-governance-actions-button", + onClick: () => navigate(PATHS.createGovernanceAction), + variant: "contained", + } as const, + ]), + // common + { + children: t("learnMore"), + dataTestId: "learn-more-button", + onClick: () => + openInNewTab( + "https://docs.sanchogov.tools/faqs/what-is-a-governance-action", + ), + }, + ]} + description={t("dashboard.cards.proposeGovernanceAction.description")} + imageURL={IMAGES.proposeGovActionImage} + isInProgressOnCard={false} + transactionId={createGovActionTx?.transactionHash} + state={createGovActionTx ? "inProgress" : "default"} + title={t("dashboard.cards.proposeGovernanceAction.title")} + /> + ); +}; diff --git a/govtool/frontend/src/components/organisms/DashboardCards/SoleVoterDashboardCard.tsx b/govtool/frontend/src/components/organisms/DashboardCards/SoleVoterDashboardCard.tsx new file mode 100644 index 000000000..68be628f1 --- /dev/null +++ b/govtool/frontend/src/components/organisms/DashboardCards/SoleVoterDashboardCard.tsx @@ -0,0 +1,154 @@ +import { useNavigate } from "react-router-dom"; +import { Trans } from "react-i18next"; + +import { IMAGES, PATHS } from "@consts"; +import { PendingTransaction } from "@context"; +import { useTranslation } from "@hooks"; +import { + DashboardActionCard, + DashboardActionCardProps, + SoleVoterAction, +} from "@molecules"; +import { correctAdaFormat, openInNewTab } from "@utils"; +import { LoadingButtonProps } from "@atoms"; +import { VoterInfo } from "@models"; + +type SoleVoterDashboardCardProps = { + dRepIDBech32: string; + pendingTransaction: PendingTransaction; + voter: VoterInfo; + votingPower: number; +}; + +export const SoleVoterDashboardCard = ({ + dRepIDBech32, + pendingTransaction, + voter, + votingPower, +}: SoleVoterDashboardCardProps) => { + const navigate = useNavigate(); + const { t } = useTranslation(); + + const ada = correctAdaFormat(votingPower); + + // TODO: Add navigation to DRep explorer + const onClickAction = () => navigate("/"); + + // learn more button + const learnMoreButton: LoadingButtonProps = { + children: t("learnMore"), + dataTestId: "learn-more-button", + onClick: () => + openInNewTab( + "https://docs.sanchogov.tools/faqs/what-does-it-mean-to-register-as-a-drep", + ), + }; + + const cardProps: Partial = (() => { + // transaction in progress + if ( + !!pendingTransaction.registerAsSoleVoter || + !!pendingTransaction.retireAsSoleVoter + ) { + return { + buttons: [learnMoreButton], + state: "inProgress", + ...(pendingTransaction.registerAsSoleVoter && { + description: t("dashboard.cards.soleVoter.registrationInProgress"), + title: t("dashboard.cards.soleVoter.registration"), + transactionId: pendingTransaction.registerAsSoleVoter.resourceId, + }), + ...(pendingTransaction.retireAsSoleVoter && { + description: t("dashboard.cards.soleVoter.retirementInProgress"), + title: t("dashboard.cards.soleVoter.retirement"), + }), + }; + } + + // currently registered + if (voter?.isRegisteredAsSoleVoter) { + return { + buttons: [ + { + children: t("dashboard.cards.soleVoter.retire"), + dataTestId: "retire-as-sole-voter-button", + onClick: () => navigate(PATHS.retireAsSoleVoter), + sx: { backgroundColor: "arcticWhite" }, + }, + { ...learnMoreButton, variant: "text" }, + ], + description: ( + + ), + state: "active", + title: t("dashboard.cards.soleVoter.youAreSoleVoterTitle"), + }; + } + + // was registered + if (voter?.wasRegisteredAsSoleVoter) { + return { + buttons: [ + { + children: t("dashboard.cards.soleVoter.reRegister"), + dataTestId: "register-as-sole-voter-button", + onClick: () => navigate(PATHS.registerAsSoleVoter), + variant: "contained", + }, + learnMoreButton, + ], + description: ( + + ), + title: t("dashboard.cards.soleVoter.wasSoleVoterTitle"), + }; + } + + // not registered + return { + buttons: [ + { + children: t("dashboard.cards.soleVoter.register"), + dataTestId: "register-as-sole-voter-button", + onClick: () => navigate(PATHS.registerAsSoleVoter), + variant: "contained", + }, + learnMoreButton, + ], + description: ( + + ), + title: t("dashboard.cards.soleVoter.registerTitle"), + }; + })(); + + return ( + + {(pendingTransaction?.registerAsSoleVoter || + voter.isRegisteredAsSoleVoter) && ( + + )} + + ); +}; diff --git a/govtool/frontend/src/components/organisms/DashboardDrawerMobile.tsx b/govtool/frontend/src/components/organisms/DashboardDrawerMobile.tsx index 05a821cdf..ee75b1df1 100644 --- a/govtool/frontend/src/components/organisms/DashboardDrawerMobile.tsx +++ b/govtool/frontend/src/components/organisms/DashboardDrawerMobile.tsx @@ -2,9 +2,8 @@ import { Box, Grid, IconButton, SwipeableDrawer } from "@mui/material"; import { Background, Link } from "@atoms"; import { CONNECTED_NAV_ITEMS, ICONS } from "@consts"; -import { useCardano } from "@context"; import { DRepInfoCard, WalletInfoCard } from "@molecules"; -import { useScreenDimension } from "@hooks"; +import { useGetVoterInfo, useScreenDimension } from "@hooks"; import { openInNewTab } from "@utils"; import { DashboardDrawerMobileProps } from "./types"; @@ -18,7 +17,7 @@ export const DashboardDrawerMobile = ({ setIsDrawerOpen, }: DashboardDrawerMobileProps) => { const { screenWidth } = useScreenDimension(); - const { voter } = useCardano(); + const { voter } = useGetVoterInfo(); const openDrawer = () => { setIsDrawerOpen(true); @@ -56,13 +55,13 @@ export const DashboardDrawerMobile = ({ width: screenWidth - CALCULATED_DRAWER_PADDING, }} > - + app-logo - + drawer @@ -72,6 +71,8 @@ export const DashboardDrawerMobile = ({ {...navItem} size="big" onClick={() => { + // TODO: Refine if it is needed to remove this eslint-disable + // eslint-disable-next-line no-unused-expressions navItem.newTabLink && openInNewTab(navItem.newTabLink); setIsDrawerOpen(false); }} diff --git a/govtool/frontend/src/components/organisms/DashboardGovernanceActionDetails.tsx b/govtool/frontend/src/components/organisms/DashboardGovernanceActionDetails.tsx index 815b1f37f..426ad82b4 100644 --- a/govtool/frontend/src/components/organisms/DashboardGovernanceActionDetails.tsx +++ b/govtool/frontend/src/components/organisms/DashboardGovernanceActionDetails.tsx @@ -1,37 +1,33 @@ import { useNavigate, useLocation, - NavLink, useParams, generatePath, } from "react-router-dom"; -import { - Box, - Breadcrumbs, - CircularProgress, - Link, - Typography, -} from "@mui/material"; +import { Box, CircularProgress, Link, Typography } from "@mui/material"; import { ICONS, PATHS } from "@consts"; import { useCardano } from "@context"; import { useGetProposalQuery, + useGetVoterInfo, useScreenDimension, useTranslation, } from "@hooks"; -import { GovernanceActionDetailsCard } from "@organisms"; import { formatDisplayDate, getShortenedGovActionId, getProposalTypeLabel, } from "@utils"; +import { GovernanceActionDetailsCard } from "@organisms"; +import { Breadcrumbs } from "@molecules"; export const DashboardGovernanceActionDetails = () => { - const { voter } = useCardano(); + const { voter } = useGetVoterInfo(); + const { pendingTransaction, isEnableLoading } = useCardano(); const { state, hash } = useLocation(); const navigate = useNavigate(); - const { isMobile, screenWidth } = useScreenDimension(); + const { isMobile } = useScreenDimension(); const { t } = useTranslation(); const { proposalId } = useParams(); const fullProposalId = proposalId + hash; @@ -40,23 +36,9 @@ export const DashboardGovernanceActionDetails = () => { const shortenedGovActionId = getShortenedGovActionId( state ? state.txHash : data?.proposal.txHash ?? "", - state ? state.index : data?.proposal.index ?? "" + state ? state.index : data?.proposal.index ?? "", ); - - const breadcrumbs = [ - - - {t("govActions.title")} - - , - - {t("govActions.voteOnGovActions")} - , - ]; + const title = state ? state.title : data?.proposal.title; return ( { flex={1} > - {breadcrumbs} - + elementOne={t("govActions.title")} + elementOnePath={PATHS.dashboardGovernanceActions} + elementTwo={title} + isDataMissing={state ? state.isDataMissing : data?.isDataMissing} + /> { : PATHS.dashboardGovernanceActions, { state: { - isVotedListOnLoad: state && state.vote ? true : false, + isVotedListOnLoad: !!(state && state.vote), }, - } + }, ) } > @@ -105,16 +83,18 @@ export const DashboardGovernanceActionDetails = () => { style={{ marginRight: "12px", transform: "rotate(180deg)" }} /> - {t("backToList")} + {t("back")} - - {isLoading ? ( + + {isLoading || isEnableLoading ? ( @@ -128,34 +108,57 @@ export const DashboardGovernanceActionDetails = () => { ? formatDisplayDate(state.createdDate) : formatDisplayDate(data.proposal.createdDate) } - details={state ? state.details : data.proposal.details} + createdEpochNo={ + state ? state.createdEpochNo : data.proposal.createdEpochNo + } + isDataMissing={state ? state.isDataMissing : data?.isDataMissing} expiryDate={ state ? formatDisplayDate(state.expiryDate) : formatDisplayDate(data.proposal.expiryDate) } - isDRep={voter?.isRegisteredAsDRep || voter?.isRegisteredAsSoleVoter} + expiryEpochNo={ + state ? state.expiryEpochNo : data.proposal.expiryEpochNo + } + isVoter={ + voter?.isRegisteredAsDRep || voter?.isRegisteredAsSoleVoter + } noVotes={state ? state.noVotes : data.proposal.noVotes} type={ state ? getProposalTypeLabel(state.type) : getProposalTypeLabel(data.proposal.type) } + details={state ? state.details : data.proposal.details} url={state ? state.url : data.proposal.url} + title={state ? state.title : data.proposal.title} + about={state ? state.about : data.proposal.about} + motivation={state ? state.motivation : data.proposal.motivation} + rationale={state ? state.rationale : data.proposal.rationale} yesVotes={state ? state.yesVotes : data.proposal.yesVotes} voteFromEP={data?.vote?.vote} - shortenedGovActionId={shortenedGovActionId} + voteUrlFromEP={data?.vote?.url} + voteDateFromEP={data?.vote?.date} + voteEpochNoFromEP={data?.vote?.epochNo} + govActionId={fullProposalId} + isInProgress={ + pendingTransaction.vote?.resourceId === + fullProposalId.replace("#", "") + } + isDashboard /> ) : ( - {t("govActions.withIdNotExist.partOne")}  + {t("govActions.withIdNotExist.partOne")} +   + + + {` ${shortenedGovActionId} `} - {` ${shortenedGovActionId} `} -  {t("govActions.withIdNotExist.partTwo")} +   + {t("govActions.withIdNotExist.partTwo")} )} diff --git a/govtool/frontend/src/components/organisms/DashboardGovernanceActions.tsx b/govtool/frontend/src/components/organisms/DashboardGovernanceActions.tsx index c8b18afb6..ab224032e 100644 --- a/govtool/frontend/src/components/organisms/DashboardGovernanceActions.tsx +++ b/govtool/frontend/src/components/organisms/DashboardGovernanceActions.tsx @@ -1,22 +1,33 @@ -import { useState, useCallback, useEffect } from "react"; +import { useState, useEffect } from "react"; import { Box, CircularProgress, Tab, Tabs, styled } from "@mui/material"; import { useLocation } from "react-router-dom"; +import { GOVERNANCE_ACTIONS_FILTERS, GOVERNANCE_ACTIONS_SORTING } from "@consts"; import { useCardano } from "@context"; -import { useScreenDimension, useTranslation } from "@hooks"; +import { + useDataActionsBar, + useGetProposalsQuery, + useGetVoterInfo, + useScreenDimension, + useTranslation, +} from "@hooks"; import { DataActionsBar } from "@molecules"; import { GovernanceActionsToVote, DashboardGovernanceActionsVotedOn, } from "@organisms"; -interface TabPanelProps { +type TabPanelProps = { children?: React.ReactNode; index: number; value: number; -} +}; -function CustomTabPanel(props: TabPanelProps) { +const defaultCategories = GOVERNANCE_ACTIONS_FILTERS.map( + (category) => category.key, +); + +const CustomTabPanel = (props: TabPanelProps) => { const { children, value, index } = props; return ( @@ -34,7 +45,7 @@ function CustomTabPanel(props: TabPanelProps) { {value === index && {children}} ); -} +}; type StyledTabProps = { label: string; @@ -54,33 +65,31 @@ const StyledTab = styled((props: StyledTabProps) => ( })); export const DashboardGovernanceActions = () => { - const [searchText, setSearchText] = useState(""); - const [filtersOpen, setFiltersOpen] = useState(false); - const [chosenFilters, setChosenFilters] = useState([]); - const [sortOpen, setSortOpen] = useState(false); - const [chosenSorting, setChosenSorting] = useState(""); + const { debouncedSearchText, ...dataActionsBarProps } = useDataActionsBar(); + const { chosenFilters, chosenSorting } = dataActionsBarProps; + const { voter } = useGetVoterInfo(); + const { isMobile } = useScreenDimension(); + const { t } = useTranslation(); + const { isEnableLoading } = useCardano(); + + const queryFilters = + chosenFilters.length > 0 ? chosenFilters : defaultCategories; + + const { proposals, isProposalsLoading } = useGetProposalsQuery({ + filters: queryFilters, + sorting: chosenSorting, + searchPhrase: debouncedSearchText, + }); const { state } = useLocation(); const [content, setContent] = useState( - state && state.isVotedListOnLoad ? 1 : 0 + state && state.isVotedListOnLoad ? 1 : 0, ); - const { voter, isDrepLoading } = useCardano(); - const { isMobile } = useScreenDimension(); - const { t } = useTranslation(); - const handleChange = (_event: React.SyntheticEvent, newValue: number) => { setContent(newValue); }; - const closeFilters = useCallback(() => { - setFiltersOpen(false); - }, [setFiltersOpen]); - - const closeSorts = useCallback(() => { - setSortOpen(false); - }, [setSortOpen]); - useEffect(() => { window.history.replaceState({}, document.title); }, []); @@ -93,83 +102,76 @@ export const DashboardGovernanceActions = () => { display="flex" flexDirection="column" > - {isDrepLoading ? ( - - - - ) : ( - <> - - {(voter?.isRegisteredAsDRep || voter?.isRegisteredAsSoleVoter) && ( - - + + {!proposals || !voter || isEnableLoading || isProposalsLoading ? ( + + + + ) : ( + <> + {(voter?.isRegisteredAsDRep || voter?.isRegisteredAsSoleVoter) && ( + + + + + )} + + + - + + - - )} - - - - - - - - - )} + + + )} + ); }; diff --git a/govtool/frontend/src/components/organisms/DashboardGovernanceActionsVotedOn.tsx b/govtool/frontend/src/components/organisms/DashboardGovernanceActionsVotedOn.tsx index 8bed76c0d..145f2e4cf 100644 --- a/govtool/frontend/src/components/organisms/DashboardGovernanceActionsVotedOn.tsx +++ b/govtool/frontend/src/components/organisms/DashboardGovernanceActionsVotedOn.tsx @@ -1,22 +1,21 @@ import { useMemo } from "react"; import { Box, Typography, CircularProgress } from "@mui/material"; -import { GovernanceVotedOnCard } from "@molecules"; -import { Slider } from "."; +import { useCardano } from "@context"; import { useGetDRepVotesQuery, useScreenDimension, useTranslation, } from "@hooks"; -import { getProposalTypeLabel } from "@/utils/getProposalTypeLabel"; -import { getFullGovActionId } from "@/utils"; -import { useCardano } from "@/context"; +import { GovernanceVotedOnCard } from "@molecules"; +import { Slider } from "@organisms"; +import { getFullGovActionId, getProposalTypeLabel } from "@utils"; -interface DashboardGovernanceActionsVotedOnProps { +type DashboardGovernanceActionsVotedOnProps = { filters: string[]; searchPhrase?: string; sorting: string; -} +}; export const DashboardGovernanceActionsVotedOn = ({ filters, @@ -25,30 +24,24 @@ export const DashboardGovernanceActionsVotedOn = ({ }: DashboardGovernanceActionsVotedOnProps) => { const { data, areDRepVotesLoading } = useGetDRepVotesQuery(filters, sorting); const { isMobile } = useScreenDimension(); - const { voteTransaction } = useCardano(); + const { pendingTransaction } = useCardano(); const { t } = useTranslation(); const filteredData = useMemo(() => { if (data.length && searchPhrase) { return data - .map((entry) => { - return { - ...entry, - actions: entry.actions.filter((action) => { - return getFullGovActionId( - action.proposal.txHash, - action.proposal.index - ) - .toLowerCase() - .includes(searchPhrase.toLowerCase()); - }), - }; - }) + .map((entry) => ({ + ...entry, + actions: entry.actions.filter((action) => + getFullGovActionId(action.proposal.txHash, action.proposal.index) + .toLowerCase() + .includes(searchPhrase.toLowerCase()), + ), + })) .filter((entry) => entry.actions.length > 0); - } else { - return data; } - }, [data, searchPhrase, voteTransaction.transactionHash]); + return data; + }, [data, searchPhrase, pendingTransaction.vote]); return areDRepVotesLoading ? ( @@ -70,28 +63,26 @@ export const DashboardGovernanceActionsVotedOn = ({
    { - return ( -
    - -
    - ); - })} + dataLength={item.actions.slice(0, 6).length} + onDashboard + data={item.actions.map((action) => ( +
    + +
    + ))} />
    diff --git a/govtool/frontend/src/components/organisms/DashboardTopNav.tsx b/govtool/frontend/src/components/organisms/DashboardTopNav.tsx index bcfb5baaa..787735251 100644 --- a/govtool/frontend/src/components/organisms/DashboardTopNav.tsx +++ b/govtool/frontend/src/components/organisms/DashboardTopNav.tsx @@ -62,7 +62,12 @@ export const DashboardTopNav = ({ > {isMobile ? ( - + app-logo ) : null} {!isMobile && title ? ( {title} @@ -76,7 +81,7 @@ export const DashboardTopNav = ({ sx={{ padding: 0, marginLeft: 1 }} onClick={openDrawer} > - + drawer )} diff --git a/govtool/frontend/src/components/organisms/DelegateTodRepStepOne.tsx b/govtool/frontend/src/components/organisms/DelegateTodRepStepOne.tsx index 3f38f8ba8..6524497d4 100644 --- a/govtool/frontend/src/components/organisms/DelegateTodRepStepOne.tsx +++ b/govtool/frontend/src/components/organisms/DelegateTodRepStepOne.tsx @@ -8,11 +8,12 @@ import { useCardano, useModal } from "@context"; import { useGetAdaHolderCurrentDelegationQuery, useGetAdaHolderVotingPowerQuery, + useGetVoterInfo, useScreenDimension, useTranslation, } from "@hooks"; -import { theme } from "@/theme"; import { correctAdaFormat } from "@utils"; +import { theme } from "@/theme"; interface DelegateProps { setStep: (newStep: number) => void; @@ -21,12 +22,12 @@ interface DelegateProps { export const DelegateTodRepStepOne = ({ setStep }: DelegateProps) => { const navigate = useNavigate(); const { - voter, dRepID, buildSignSubmitConwayCertTx, buildVoteDelegationCert, stakeKey, } = useCardano(); + const { voter } = useGetVoterInfo(); const { currentDelegation } = useGetAdaHolderCurrentDelegationQuery(stakeKey); const { openModal, closeModal } = useModal(); const [areOptions, setAreOptions] = useState(false); @@ -93,9 +94,11 @@ export const DelegateTodRepStepOne = ({ setStep }: DelegateProps) => { const certBuilder = await buildVoteDelegationCert(chosenOption); const result = await buildSignSubmitConwayCertTx({ certBuilder, - type: "delegation", + type: "delegate", + resourceId: chosenOption, }); if (result) openSuccessDelegationModal(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (error: any) { const errorMessage = error.info ? error.info : error; @@ -105,8 +108,8 @@ export const DelegateTodRepStepOne = ({ setStep }: DelegateProps) => { } }, [chosenOption, buildSignSubmitConwayCertTx, buildVoteDelegationCert]); - const renderDelegateButton = useMemo(() => { - return ( + const renderDelegateButton = useMemo( + () => ( { > {chosenOption !== dRepID ? t("nextStep") : t("delegate")} - ); - }, [ - chosenOption, - delegate, - voter?.isRegisteredAsDRep, - voter?.isRegisteredAsSoleVoter, - dRepID, - isDelegationLoading, - isMobile, - ]); + ), + [ + chosenOption, + delegate, + voter?.isRegisteredAsDRep, + voter?.isRegisteredAsSoleVoter, + dRepID, + isDelegationLoading, + isMobile, + ], + ); - const renderCancelButton = useMemo(() => { - return ( + const renderCancelButton = useMemo( + () => ( - ); - }, [isMobile]); + ), + [isMobile], + ); return ( { px={isMobile ? 2 : 17.5} pb={isMobile ? 4 : 5} pt={isMobile ? 4 : 8.5} - borderRadius={"20px"} + borderRadius="20px" mb={isMobile ? 0 : 6} - height={"100%"} + height="100%" > {!isMobile && ( @@ -223,7 +228,7 @@ export const DelegateTodRepStepOne = ({ setStep }: DelegateProps) => { onChange={setChosenOption} tooltipTitle={t("tooltips.delegateTodRep.toMyself.heading")} tooltipText={t( - "tooltips.delegateTodRep.toMyself.paragraphOne" + "tooltips.delegateTodRep.toMyself.paragraphOne", )} isChecked={chosenOption === dRepID} subtitle={t("delegation.toMyself.subtitle")} @@ -275,10 +280,10 @@ export const DelegateTodRepStepOne = ({ setStep }: DelegateProps) => { { onChange={setChosenOption} tooltipTitle={t("tooltips.delegateTodRep.abstain.heading")} tooltipText={t( - "tooltips.delegateTodRep.abstain.paragraphOne" + "tooltips.delegateTodRep.abstain.paragraphOne", )} isChecked={chosenOption === "abstain"} subtitle={t("delegation.abstain.subtitle")} @@ -307,9 +312,9 @@ export const DelegateTodRepStepOne = ({ setStep }: DelegateProps) => { {isMobile ? renderDelegateButton : renderCancelButton} diff --git a/govtool/frontend/src/components/organisms/DelegateTodRepStepTwo.tsx b/govtool/frontend/src/components/organisms/DelegateTodRepStepTwo.tsx index f75a3c83d..7ac503d7a 100644 --- a/govtool/frontend/src/components/organisms/DelegateTodRepStepTwo.tsx +++ b/govtool/frontend/src/components/organisms/DelegateTodRepStepTwo.tsx @@ -1,14 +1,14 @@ import { useMemo } from "react"; import { Box, Link } from "@mui/material"; -import { Button, LoadingButton, Typography } from "../atoms"; import { useScreenDimension, useDelegateTodRepForm, useTranslation, } from "@hooks"; -import { theme } from "@/theme"; import { openInNewTab } from "@utils"; +import { Button, LoadingButton, Typography } from "../atoms"; +import { theme } from "@/theme"; import { ControlledField } from "."; interface DelegateProps { @@ -31,10 +31,10 @@ export const DelegateTodRepStepTwo = ({ setStep }: DelegateProps) => { isDelegationLoading, } = useDelegateTodRepForm(); - const renderDelegateButton = useMemo(() => { - return ( + const renderDelegateButton = useMemo( + () => ( { > {t("delegate")} - ); - }, [isDelegateButtonDisabled, delegate, isMobile, isDelegationLoading]); + ), + [isDelegateButtonDisabled, delegate, isMobile, isDelegationLoading], + ); - const renderBackButton = useMemo(() => { - return ( + const renderBackButton = useMemo( + () => ( - ); - }, [isMobile]); + ), + [isMobile], + ); return ( { /> openInNewTab( - "https://docs.sanchogov.tools/faqs/where-can-i-find-a-drep-id" + "https://docs.sanchogov.tools/faqs/where-can-i-find-a-drep-id", ) } - alignSelf={"center"} + alignSelf="center" mt={4} sx={[{ "&:hover": { cursor: "pointer" } }]} > @@ -115,9 +117,9 @@ export const DelegateTodRepStepTwo = ({ setStep }: DelegateProps) => { diff --git a/govtool/frontend/src/components/organisms/Drawer.tsx b/govtool/frontend/src/components/organisms/Drawer.tsx index 4eb123f5e..b69c1dd51 100644 --- a/govtool/frontend/src/components/organisms/Drawer.tsx +++ b/govtool/frontend/src/components/organisms/Drawer.tsx @@ -1,38 +1,38 @@ -import { Box, Grid } from "@mui/material"; -import { NavLink } from "react-router-dom"; +import { Box, Grid } from '@mui/material'; +import { NavLink } from 'react-router-dom'; import { DrawerLink, Typography } from "@atoms"; import { CONNECTED_NAV_ITEMS, ICONS, IMAGES, PATHS } from "@consts"; -import { useCardano } from "@context"; -import { useTranslation } from "@hooks"; +import { useGetVoterInfo, useTranslation } from "@hooks"; import { WalletInfoCard, DRepInfoCard } from "@molecules"; import { openInNewTab } from "@utils"; export const Drawer = () => { - const { voter } = useCardano(); + const { voter } = useGetVoterInfo(); const { t } = useTranslation(); return ( app-logo { openInNewTab( - "https://docs.sanchogov.tools/support/get-help-in-discord" + "https://docs.sanchogov.tools/support/get-help-in-discord", ) } /> - {t("footer.copyright")} + {t('footer.copyright')} diff --git a/govtool/frontend/src/components/organisms/DrawerMobile.tsx b/govtool/frontend/src/components/organisms/DrawerMobile.tsx index d5606fe94..6fd27d050 100644 --- a/govtool/frontend/src/components/organisms/DrawerMobile.tsx +++ b/govtool/frontend/src/components/organisms/DrawerMobile.tsx @@ -47,12 +47,12 @@ export const DrawerMobile = ({ width: screenWidth - CALCULATED_DRAWER_PADDING, }} > - + app-logo setIsDrawerOpen(false)} > - + close-drawer {isConnectButton ? ( @@ -93,7 +93,7 @@ export const DrawerMobile = ({ onClick={onClickHelp} sx={{ alignItems: "center", display: "flex", p: DRAWER_PADDING }} > - + help {t("menu.help")} diff --git a/govtool/frontend/src/components/organisms/EditDRepInfoSteps/EditDRepForm.tsx b/govtool/frontend/src/components/organisms/EditDRepInfoSteps/EditDRepForm.tsx new file mode 100644 index 000000000..fcc821727 --- /dev/null +++ b/govtool/frontend/src/components/organisms/EditDRepInfoSteps/EditDRepForm.tsx @@ -0,0 +1,152 @@ +import { Dispatch, SetStateAction, useCallback } from "react"; +import { useFieldArray } from "react-hook-form"; +import { Box } from "@mui/material"; +import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline"; + +import { Button, InfoText, Spacer, Typography } from "@atoms"; +import { Placeholders, Rules } from "@consts"; +import { + useEditDRepInfoForm, + useScreenDimension, + useTranslation, +} from "@hooks"; + +import { BgCard, ControlledField } from ".."; + +const MAX_NUMBER_OF_LINKS = 7; + +export const EditDRepForm = ({ + onClickCancel, + setStep, +}: { + onClickCancel: () => void; + setStep: Dispatch>; +}) => { + const { t } = useTranslation(); + const { isMobile } = useScreenDimension(); + const { control, errors, isError, register, watch } = useEditDRepInfoForm(); + const { + append, + fields: links, + remove, + } = useFieldArray({ + control, + name: "links", + }); + + const onClickContinue = () => setStep(2); + + const addLink = useCallback(() => append({ link: "" }), [append]); + + const removeLink = useCallback((index: number) => remove(index), [remove]); + + const isContinueButtonDisabled = !watch("dRepName") || isError; + + const renderLinks = useCallback( + () => + links.map((field, index) => ( + 1 ? ( + removeLink(index)} + /> + ) : null + } + key={field.id} + // prefer-template rule for that label makes no sense + // eslint-disable-next-line prefer-template + label={t("forms.link") + ` ${index + 1}`} + layoutStyles={{ mb: 3 }} + placeholder={Placeholders.LINK} + name={`links.${index}.link`} + rules={Rules.LINK} + /> + )), + [errors, links], + ); + + return ( + + + + + {t("editMetadata.dRepName")} + + + {t("editMetadata.dRepNameDescription")} + + + + + + + + {t("editMetadata.aboutYou")} + + + {t("editMetadata.aboutYouDescription")} + + + + + + +

    + {t("editMetadata.linksDescription")} + + {t("editMetadata.maximumLinks", { + numberOfLinks: MAX_NUMBER_OF_LINKS, + })} + +

    + + {renderLinks()} + {links?.length < MAX_NUMBER_OF_LINKS ? ( + + ) : null} + +
    + ); +}; diff --git a/govtool/frontend/src/components/organisms/EditDRepInfoSteps/EditDRepStorageInformation.tsx b/govtool/frontend/src/components/organisms/EditDRepInfoSteps/EditDRepStorageInformation.tsx new file mode 100644 index 000000000..5a7b6293c --- /dev/null +++ b/govtool/frontend/src/components/organisms/EditDRepInfoSteps/EditDRepStorageInformation.tsx @@ -0,0 +1,129 @@ +import { Dispatch, SetStateAction, useEffect } from "react"; +import { Box } from "@mui/material"; +import OpenInNewIcon from "@mui/icons-material/OpenInNew"; + +import { Button, Spacer, Typography } from "@atoms"; +import { ICONS, Rules } from "@consts"; +import { + useEditDRepInfoForm, + useTranslation, + useScreenDimension, +} from "@hooks"; +import { Step } from "@molecules"; +import { BgCard, ControlledField } from "@organisms"; +import { openInNewTab } from "@utils"; + +type StorageInformationProps = { + setStep: Dispatch>; +}; + +export const EditDRepStorageInformation = ({ + setStep, +}: StorageInformationProps) => { + const { t } = useTranslation(); + const { + control, + errors, + generateMetadata, + getValues, + isEditDRepMetadataLoading, + onClickDownloadJson, + editDRepInfo, + watch, + } = useEditDRepInfoForm(); + const { screenWidth } = useScreenDimension(); + + const fileName = getValues("dRepName"); + + // TODO: Change link to correct + const openGuideAboutStoringInformation = () => + openInNewTab("https://sancho.network/"); + + const isActionButtonDisabled = !watch("storingURL") || !!errors.storingURL; + + const onClickBack = () => setStep(2); + + useEffect(() => { + generateMetadata(); + }, []); + + return ( + + + {t("editMetadata.storingInformationTitle")} + + + + {t("editMetadata.storingInformationDescription")} + + + } + sx={{ + width: "fit-content", + ml: screenWidth < 1024 ? 0 : 1.75, + mt: screenWidth < 1024 ? 1.5 : 0, + }} + variant="outlined" + > + {`${fileName}.jsonld`} + + } + label={t("editMetadata.storingInformationStep1Label")} + componentsLayoutStyles={{ + alignItems: screenWidth < 1024 ? undefined : "center", + flexDirection: screenWidth < 1024 ? "column" : "row", + }} + stepNumber={1} + /> + + + + + } + label={t("editMetadata.storingInformationStep3Label")} + stepNumber={3} + /> + + + ); +}; diff --git a/govtool/frontend/src/components/organisms/EditDRepInfoSteps/EditDRepStoreDataInfo.tsx b/govtool/frontend/src/components/organisms/EditDRepInfoSteps/EditDRepStoreDataInfo.tsx new file mode 100644 index 000000000..24afb4120 --- /dev/null +++ b/govtool/frontend/src/components/organisms/EditDRepInfoSteps/EditDRepStoreDataInfo.tsx @@ -0,0 +1,65 @@ +import { Dispatch, SetStateAction } from "react"; +import { Box, Link } from "@mui/material"; + +import { Spacer, Typography } from "@atoms"; +import { + useScreenDimension, + useTranslation, + useEditDRepInfoForm, +} from "@hooks"; +import { openInNewTab } from "@utils"; + +import { BgCard, ControlledField } from ".."; + +export const EditDRepStoreDataInfo = ({ + setStep, +}: { + setStep: Dispatch>; +}) => { + const { t } = useTranslation(); + const { isMobile } = useScreenDimension(); + const { control, errors, watch } = useEditDRepInfoForm(); + + const onClickBackButton = () => setStep(1); + + const onClickContinue = () => setStep(3); + + const isContinueDisabled = !watch("storeData"); + + // TODO: Add link about store data when available + const openLink = () => openInNewTab("https://sancho.network/get-started"); + + return ( + + + {t("editMetadata.storeDataTitle")} + + + {t("editMetadata.storeDataLink")} + + + + + + ); +}; diff --git a/govtool/frontend/src/components/organisms/EditDRepInfoSteps/index.ts b/govtool/frontend/src/components/organisms/EditDRepInfoSteps/index.ts new file mode 100644 index 000000000..65ee7cc1f --- /dev/null +++ b/govtool/frontend/src/components/organisms/EditDRepInfoSteps/index.ts @@ -0,0 +1,3 @@ +export * from "./EditDRepForm"; +export * from "./EditDRepStorageInformation"; +export * from "./EditDRepStoreDataInfo"; diff --git a/govtool/frontend/src/components/organisms/ExternalLinkModal.tsx b/govtool/frontend/src/components/organisms/ExternalLinkModal.tsx index 2a111ed6f..e49f5b625 100644 --- a/govtool/frontend/src/components/organisms/ExternalLinkModal.tsx +++ b/govtool/frontend/src/components/organisms/ExternalLinkModal.tsx @@ -4,14 +4,14 @@ import { ModalContents, ModalHeader, ModalWrapper } from "@atoms"; import { IMAGES } from "@consts"; import { useModal } from "@context"; import { useScreenDimension, useTranslation } from "@hooks"; -import { theme } from "@/theme"; import { openInNewTab } from "@utils"; +import { theme } from "@/theme"; export interface ExternalLinkModalState { externalLink: string; } -export function ExternalLinkModal() { +export const ExternalLinkModal = () => { const { state, closeModal } = useModal(); const { isMobile } = useScreenDimension(); const { t } = useTranslation(); @@ -32,7 +32,7 @@ export function ExternalLinkModal() { {t( - `modals.externalLink.${isMobile ? "thisIs" : "youAreAboutToOpen"}` + `modals.externalLink.${isMobile ? "thisIs" : "youAreAboutToOpen"}`, )} -
    - - {isDRep ? ( - - ) : ( - - )} - + {(isVoteSubmitted || isInProgress) && ( + + )} + +
    ); }; diff --git a/govtool/frontend/src/components/organisms/GovernanceActionDetailsCardData.tsx b/govtool/frontend/src/components/organisms/GovernanceActionDetailsCardData.tsx new file mode 100644 index 000000000..617979d9a --- /dev/null +++ b/govtool/frontend/src/components/organisms/GovernanceActionDetailsCardData.tsx @@ -0,0 +1,126 @@ +import { Box } from "@mui/material"; + +import { ExternalModalButton } from "@atoms"; +import { + GovernanceActionCardElement, + GovernanceActionDetailsCardLinks, + DataMissingInfoBox, + GovernanceActionDetailsCardHeader, + GovernanceActionsDatesBox, + GovernanceActionDetailsCardOnChainData, +} from "@molecules"; +import { useScreenDimension, useTranslation } from "@hooks"; +import { GAMetedataErrors, getProposalTypeNoEmptySpaces } from "@utils"; + +type GovernanceActionDetailsCardDataProps = { + type: string; + govActionId: string; + createdDate: string; + createdEpochNo: number; + expiryDate: string; + expiryEpochNo: number; + details?: ActionDetailsType; + url: string; + title?: string; + about?: string; + motivation?: string; + rationale?: string; + isDataMissing: boolean | GAMetedataErrors; + isOneColumn: boolean; + isDashboard?: boolean; + isInProgress?: boolean; + isSubmitted?: boolean; +}; + +export const GovernanceActionDetailsCardData = ({ + type, + govActionId, + createdDate, + createdEpochNo, + expiryDate, + expiryEpochNo, + details, + url, + title, + about, + motivation, + rationale, + isDataMissing, + isOneColumn, + isDashboard, + isInProgress, + isSubmitted, +}: GovernanceActionDetailsCardDataProps) => { + const { t } = useTranslation(); + const { screenWidth } = useScreenDimension(); + + const isModifiedPadding = + (isDashboard && screenWidth < 1168) ?? screenWidth < 900; + + return ( + + + + + + {isDataMissing && ( + + )} + + + + + {details && Object.keys(details).length !== 0 && ( + + )} + + + ); +}; diff --git a/govtool/frontend/src/components/organisms/GovernanceActionsToVote.tsx b/govtool/frontend/src/components/organisms/GovernanceActionsToVote.tsx index c457062c5..6367de288 100644 --- a/govtool/frontend/src/components/organisms/GovernanceActionsToVote.tsx +++ b/govtool/frontend/src/components/organisms/GovernanceActionsToVote.tsx @@ -1,169 +1,117 @@ -import { useMemo } from "react"; import { useNavigate, generatePath } from "react-router-dom"; -import { Box, CircularProgress } from "@mui/material"; - -import { Slider } from "./Slider"; +import { Box } from "@mui/material"; import { Typography } from "@atoms"; -import { - useGetProposalsQuery, - useScreenDimension, - useTranslation, -} from "@hooks"; -import { GovernanceActionCard } from "@molecules"; -import { GOVERNANCE_ACTIONS_FILTERS, PATHS } from "@consts"; +import { PATHS } from "@consts"; import { useCardano } from "@context"; +import { useScreenDimension, useTranslation } from "@hooks"; +import { GovernanceActionCard } from "@molecules"; import { getProposalTypeLabel, getFullGovActionId, openInNewTab } from "@utils"; +import { Slider } from "@organisms"; type GovernanceActionsToVoteProps = { filters: string[]; + sorting: string; + proposals: { title: string; actions: ActionTypeToDsiplay[] }[]; onDashboard?: boolean; searchPhrase?: string; - sorting: string; }; -const defaultCategories = GOVERNANCE_ACTIONS_FILTERS.map( - (category) => category.key -); - export const GovernanceActionsToVote = ({ filters, onDashboard = true, - searchPhrase = "", + proposals, + searchPhrase, sorting, }: GovernanceActionsToVoteProps) => { - const { voteTransaction } = useCardano(); + const { pendingTransaction } = useCardano(); const navigate = useNavigate(); const { isMobile } = useScreenDimension(); const { t } = useTranslation(); - const queryFilters = filters.length > 0 ? filters : defaultCategories; - - const { proposals, isProposalsLoading } = useGetProposalsQuery({ - filters: queryFilters, - sorting, - }); - - const groupedByType = (data?: ActionType[]) => { - return data?.reduce((groups, item) => { - const itemType = item.type; - - if (!groups[itemType]) { - groups[itemType] = { - title: itemType, - actions: [], - }; - } - - groups[itemType].actions.push(item); - - return groups; - }, {}); - }; - - const mappedData = useMemo(() => { - const groupedData = groupedByType( - proposals?.filter((i) => - getFullGovActionId(i.txHash, i.index) - .toLowerCase() - .includes(searchPhrase.toLowerCase()) - ) - ); - return Object.values(groupedData ?? []) as ToVoteDataType; - }, [proposals, searchPhrase]); - - return !isProposalsLoading ? ( + return ( <> - {!mappedData.length ? ( + {!proposals.length ? ( {t("govActions.noResultsForTheSearch")} ) : ( <> - {mappedData?.map((item, index) => { - return ( - - { - return ( -
    - - onDashboard && - voteTransaction?.proposalId === - item?.txHash + item?.index - ? openInNewTab( - "https://adanordic.com/latest_transactions" - ) - : navigate( - onDashboard - ? generatePath( - PATHS.dashboardGovernanceActionsAction, - { - proposalId: getFullGovActionId( - item.txHash, - item.index - ), - } - ) - : PATHS.governanceActionsAction.replace( - ":proposalId", - getFullGovActionId( - item.txHash, - item.index - ) - ), + {proposals?.map((item, index) => ( + + ( +
    + { + if ( + onDashboard && + pendingTransaction.vote?.resourceId === + `${action.txHash ?? ""}${action.index ?? ""}` + ) { + openInNewTab( + "https://adanordic.com/latest_transactions", + ); + } else { + navigate( + onDashboard + ? generatePath( + PATHS.dashboardGovernanceActionsAction, { - state: { ...item }, - } + proposalId: getFullGovActionId( + action.txHash, + action.index, + ), + }, ) - } - /> -
    - ); - })} - dataLength={item.actions.slice(0, 6).length} - filters={filters} - navigateKey={item.title} - notSlicedDataLength={item.actions.length} - onDashboard={onDashboard} - searchPhrase={searchPhrase} - sorting={sorting} - title={getProposalTypeLabel(item.title)} - /> - {index < mappedData.length - 1 && ( - - )} - - ); - })} + : PATHS.governanceActionsAction.replace( + ":proposalId", + getFullGovActionId( + action.txHash, + action.index, + ), + ), + { + state: { + ...action, + isDataMissing: action.isDataMissing, + }, + }, + ); + } + }} + /> +
    + ))} + dataLength={item.actions.slice(0, 6).length} + filters={filters} + navigateKey={item.title} + notSlicedDataLength={item.actions.length} + onDashboard={onDashboard} + searchPhrase={searchPhrase} + sorting={sorting} + title={getProposalTypeLabel(item.title)} + /> + {index < proposals.length - 1 && ( + + )} + + ))} )} - ) : ( - - - ); }; diff --git a/govtool/frontend/src/components/organisms/Hero.tsx b/govtool/frontend/src/components/organisms/Hero.tsx index baae0bca4..6ef7aa1b9 100644 --- a/govtool/frontend/src/components/organisms/Hero.tsx +++ b/govtool/frontend/src/components/organisms/Hero.tsx @@ -20,7 +20,7 @@ export const Hero = () => { const onClickVotingPower = useCallback( () => openInNewTab("https://docs.sanchogov.tools/faqs/what-is-voting-power"), - [] + [], ); return ( @@ -60,9 +60,9 @@ export const Hero = () => { sx={{ cursor: "pointer", }} - >, + />, ]} - > + />
    + + {t("registration.storingInformationDescription")} + + + } + sx={{ + width: "fit-content", + ml: screenWidth < 1024 ? 0 : 1.75, + mt: screenWidth < 1024 ? 1.5 : 0, + }} + variant="outlined" + > + {`${fileName}.jsonld`} + + } + label={t("registration.storingInformationStep1Label")} + componentsLayoutStyles={{ + alignItems: screenWidth < 1024 ? undefined : "center", + flexDirection: screenWidth < 1024 ? "column" : "row", + }} + stepNumber={1} + /> + + + + + } + label={t("registration.storingInformationStep3Label")} + stepNumber={3} + /> + +
    + ); +}; diff --git a/govtool/frontend/src/components/organisms/RegisterAsdRepStepThree.tsx b/govtool/frontend/src/components/organisms/RegisterAsDRepSteps/DRepStoreDataInfo.tsx similarity index 76% rename from govtool/frontend/src/components/organisms/RegisterAsdRepStepThree.tsx rename to govtool/frontend/src/components/organisms/RegisterAsDRepSteps/DRepStoreDataInfo.tsx index 6b5a3df4d..51fed7e13 100644 --- a/govtool/frontend/src/components/organisms/RegisterAsdRepStepThree.tsx +++ b/govtool/frontend/src/components/organisms/RegisterAsDRepSteps/DRepStoreDataInfo.tsx @@ -3,37 +3,29 @@ import { Box, Link } from "@mui/material"; import { Spacer, Typography } from "@atoms"; import { - useRegisterAsdRepFormContext, useScreenDimension, useTranslation, + useRegisterAsdRepForm, } from "@hooks"; import { openInNewTab } from "@utils"; -import { BgCard, ControlledField } from "."; +import { BgCard, ControlledField } from ".."; -export const RegisterAsdRepStepThree = ({ +export const DRepStoreDataInfo = ({ setStep, }: { setStep: Dispatch>; }) => { const { t } = useTranslation(); const { isMobile } = useScreenDimension(); - const { - control, - errors, - isRegistrationAsDRepLoading, - resetField, - submitForm, - watch, - } = useRegisterAsdRepFormContext(); + const { control, errors, watch } = useRegisterAsdRepForm(); - const onClickBackButton = () => { - setStep(2); - resetField("storeData"); - }; + const onClickBackButton = () => setStep(2); const isContinueDisabled = !watch("storeData"); + const onClickContinue = () => setStep(4); + // TODO: Add link about store data when available const openLink = () => openInNewTab("https://sancho.network/get-started"); @@ -41,8 +33,7 @@ export const RegisterAsdRepStepThree = ({ diff --git a/govtool/frontend/src/components/organisms/RegisterAsDRepSteps/RegisterAsDRepForm.tsx b/govtool/frontend/src/components/organisms/RegisterAsDRepSteps/RegisterAsDRepForm.tsx new file mode 100644 index 000000000..f6f561bb4 --- /dev/null +++ b/govtool/frontend/src/components/organisms/RegisterAsDRepSteps/RegisterAsDRepForm.tsx @@ -0,0 +1,159 @@ +import { Dispatch, SetStateAction, useCallback } from "react"; +import { useFieldArray } from "react-hook-form"; +import { Box } from "@mui/material"; +import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline"; + +import { Button, InfoText, Spacer, Typography } from "@atoms"; +import { Placeholders, Rules } from "@consts"; +import { + useRegisterAsdRepForm, + useScreenDimension, + useTranslation, +} from "@hooks"; +import { VoterInfo } from "@models"; + +import { BgCard, ControlledField } from ".."; + +const MAX_NUMBER_OF_LINKS = 7; + +export const RegisterAsDRepForm = ({ + onClickCancel, + setStep, + voter, +}: { + onClickCancel: () => void; + setStep: Dispatch>; + voter?: VoterInfo; +}) => { + const { t } = useTranslation(); + const { isMobile } = useScreenDimension(); + const { control, errors, isError, register, watch } = useRegisterAsdRepForm(); + const { + append, + fields: links, + remove, + } = useFieldArray({ + control, + name: "links", + }); + + const onClickContinue = () => setStep(3); + + const onClickBack = () => setStep(1); + + const addLink = useCallback(() => append({ link: "" }), [append]); + + const removeLink = useCallback((index: number) => remove(index), [remove]); + + const isContinueButtonDisabled = !watch("dRepName") || isError; + + const renderLinks = useCallback( + () => + links.map((field, index) => ( + 1 ? ( + removeLink(index)} + /> + ) : null + } + key={field.id} + // prefer-template rule for that label makes no sense + // eslint-disable-next-line prefer-template + label={t("forms.link") + ` ${index + 1}`} + layoutStyles={{ mb: 3 }} + placeholder={Placeholders.LINK} + name={`links.${index}.link`} + rules={Rules.LINK} + /> + )), + [errors, links], + ); + + return ( + + + + + {t("registration.dRepName")} + + + {t("registration.dRepNameDescription")} + + + + + + + + {t("registration.aboutYou")} + + + {t("registration.aboutYouDescription")} + + + + + + +

    + {t("registration.linksDescription")} + + {t("registration.maximumLinks", { + numberOfLinks: MAX_NUMBER_OF_LINKS, + })} + +

    + + {renderLinks()} + {links?.length < MAX_NUMBER_OF_LINKS ? ( + + ) : null} + +
    + ); +}; diff --git a/govtool/frontend/src/components/organisms/RegisterAsdRepStepOne.tsx b/govtool/frontend/src/components/organisms/RegisterAsDRepSteps/RolesAndResponsibilities.tsx similarity index 74% rename from govtool/frontend/src/components/organisms/RegisterAsdRepStepOne.tsx rename to govtool/frontend/src/components/organisms/RegisterAsDRepSteps/RolesAndResponsibilities.tsx index 67702466e..da33bfc94 100644 --- a/govtool/frontend/src/components/organisms/RegisterAsdRepStepOne.tsx +++ b/govtool/frontend/src/components/organisms/RegisterAsDRepSteps/RolesAndResponsibilities.tsx @@ -1,4 +1,4 @@ -import { Dispatch, SetStateAction, useCallback } from "react"; +import { Dispatch, SetStateAction } from "react"; import { Trans } from "react-i18next"; import { Link } from "@mui/material"; @@ -11,9 +11,9 @@ import { PROTOCOL_PARAMS_KEY, } from "@utils"; -import { BgCard } from "."; +import { BgCard } from ".."; -export const RegisterAsdRepStepOne = ({ +export const RolesAndResponsibilities = ({ onClickCancel, setStep, }: { @@ -25,12 +25,10 @@ export const RegisterAsdRepStepOne = ({ const deposit = getItemFromLocalStorage(PROTOCOL_PARAMS_KEY); - const onClickContinue = useCallback(() => setStep(2), []); + const onClickContinue = () => setStep(2); - const openLearnMoreAboutDrep = useCallback( - () => openInNewTab("https://sancho.network/roles/drep"), - [] - ); + const openLearnMoreAboutDrep = () => + openInNewTab("https://sancho.network/roles/drep"); return ( {t("registration.rolesAndResponsibilitiesTitle")} @@ -46,7 +44,7 @@ export const RegisterAsdRepStepOne = ({ , ]} - i18nKey={"registration.rolesAndResponsibilitiesDescription"} + i18nKey="registration.rolesAndResponsibilitiesDescription" values={{ deposit: correctAdaFormat(deposit.drep_deposit) }} /> diff --git a/govtool/frontend/src/components/organisms/RegisterAsDRepSteps/WhatRetirementMeans.tsx b/govtool/frontend/src/components/organisms/RegisterAsDRepSteps/WhatRetirementMeans.tsx new file mode 100644 index 000000000..23f0fe766 --- /dev/null +++ b/govtool/frontend/src/components/organisms/RegisterAsDRepSteps/WhatRetirementMeans.tsx @@ -0,0 +1,112 @@ +import { useCallback, useState } from "react"; + +import { Typography } from "@atoms"; +import { useCardano, useModal } from "@context"; +import { useGetVoterInfo, useScreenDimension, useTranslation } from "@hooks"; + +import { BgCard } from ".."; + +export const WhatRetirementMeans = ({ + onClickCancel, +}: { + onClickCancel: () => void; +}) => { + const { + isPendingTransaction, + buildDRepRetirementCert, + buildSignSubmitConwayCertTx, + } = useCardano(); + const { t } = useTranslation(); + const { isMobile } = useScreenDimension(); + const { closeModal, openModal } = useModal(); + const [isRetirementLoading, setIsRetirementLoading] = + useState(false); + const { voter } = useGetVoterInfo(); + + const onSubmit = () => { + onClickCancel(); + closeModal(); + }; + + const retireAsDrep = useCallback(async () => { + try { + setIsRetirementLoading(true); + const isPendingTx = isPendingTransaction(); + if (isPendingTx) return; + if (!voter?.deposit) throw new Error(t("errors.appCannotGetDeposit")); + + const certBuilder = await buildDRepRetirementCert( + voter?.deposit.toString(), + ); + const result = await buildSignSubmitConwayCertTx({ + certBuilder, + type: "retireAsDrep", + voterDeposit: voter?.deposit.toString(), + }); + if (result) { + openModal({ + type: "statusModal", + state: { + buttonText: t("modals.common.goToDashboard"), + dataTestId: "retirement-transaction-submitted-modal", + link: "https://adanordic.com/latest_transactions", + message: t("modals.retirement.message"), + onSubmit, + status: "success", + title: t("modals.retirement.title"), + }, + }); + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (error: any) { + const errorMessage = error.info ? error.info : error; + + openModal({ + type: "statusModal", + state: { + buttonText: t("modals.common.goToDashboard"), + dataTestId: "retirement-transaction-error-modal", + message: errorMessage, + onSubmit, + status: "warning", + title: t("modals.common.oops"), + }, + }); + } finally { + setIsRetirementLoading(false); + } + }, [ + buildDRepRetirementCert, + buildSignSubmitConwayCertTx, + isPendingTransaction, + openModal, + voter?.deposit, + ]); + + return ( + + + {t("retirement.whatRetirementMeansTitle")} + + + {t("retirement.whatRetirementMeansDescription")} + + + ); +}; diff --git a/govtool/frontend/src/components/organisms/RegisterAsDRepSteps/index.ts b/govtool/frontend/src/components/organisms/RegisterAsDRepSteps/index.ts new file mode 100644 index 000000000..29b5cb726 --- /dev/null +++ b/govtool/frontend/src/components/organisms/RegisterAsDRepSteps/index.ts @@ -0,0 +1,5 @@ +export * from "./DRepStorageInformation"; +export * from "./DRepStoreDataInfo"; +export * from "./RegisterAsDRepForm"; +export * from "./RolesAndResponsibilities"; +export * from "./WhatRetirementMeans"; diff --git a/govtool/frontend/src/components/organisms/RegisterAsSoleVoterBox.tsx b/govtool/frontend/src/components/organisms/RegisterAsSoleVoterBox.tsx index f55acb106..445b0a474 100644 --- a/govtool/frontend/src/components/organisms/RegisterAsSoleVoterBox.tsx +++ b/govtool/frontend/src/components/organisms/RegisterAsSoleVoterBox.tsx @@ -6,19 +6,17 @@ import { PATHS } from "@consts"; import { RegisterAsSoleVoterBoxContent } from "@organisms"; import { CenteredBoxBottomButtons } from "@molecules"; import { useCardano, useModal } from "@context"; +import { useGetVoterInfo } from "@/hooks"; export const RegisterAsSoleVoterBox = () => { const [isLoading, setIsLoading] = useState(false); - const { - buildSignSubmitConwayCertTx, - buildDRepRegCert, - buildDRepUpdateCert, - voter, - } = useCardano(); + const { buildSignSubmitConwayCertTx, buildDRepRegCert, buildDRepUpdateCert } = + useCardano(); const navigate = useNavigate(); const { openModal, closeModal } = useModal(); const { t } = useTranslation(); + const { voter } = useGetVoterInfo(); const onRegister = useCallback(async () => { setIsLoading(true); @@ -29,10 +27,9 @@ export const RegisterAsSoleVoterBox = () => { : await buildDRepRegCert(); const result = await buildSignSubmitConwayCertTx({ certBuilder, - type: "soleVoterRegistration", - registrationType: "registration", + type: "registerAsSoleVoter", }); - if (result) + if (result) { openModal({ type: "statusModal", state: { @@ -48,6 +45,8 @@ export const RegisterAsSoleVoterBox = () => { dataTestId: "registration-transaction-submitted-modal", }, }); + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (e: any) { const errorMessage = e.info ? e.info : e; diff --git a/govtool/frontend/src/components/organisms/RegisterAsdRepStepTwo.tsx b/govtool/frontend/src/components/organisms/RegisterAsdRepStepTwo.tsx deleted file mode 100644 index f384349bf..000000000 --- a/govtool/frontend/src/components/organisms/RegisterAsdRepStepTwo.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import { Dispatch, SetStateAction, useCallback } from "react"; -import { Box, Link } from "@mui/material"; - -import { Spacer, Typography } from "@atoms"; -import { - useScreenDimension, - useRegisterAsdRepFormContext, - useTranslation, -} from "@hooks"; -import { openInNewTab } from "@utils"; - -import { BgCard, ControlledField } from "."; - -interface Props { - setStep: Dispatch>; -} - -export const RegisterAsdRepStepTwo = ({ setStep }: Props) => { - const { t } = useTranslation(); - const { isMobile } = useScreenDimension(); - const { control, errors, isContinueButtonDisabled, isSkipButton } = - useRegisterAsdRepFormContext(); - - const onClickContinue = useCallback(() => setStep(3), []); - - const onClickBackButton = useCallback(() => setStep(1), []); - - return ( - - - {t("registration.optional")} - - - {t("registration.addInformationTitle")} - - - {t("registration.addInformationDescription")} - - - - - - - openInNewTab( - "https://docs.sanchogov.tools/faqs/how-to-create-a-metadata-anchor" - ) - } - alignSelf={"center"} - my={5} - sx={{ cursor: "pointer" }} - > - - {t("forms.howCreateUrlAndHash")} - - - - - ); -}; diff --git a/govtool/frontend/src/components/organisms/RetireAsSoleVoterBox.tsx b/govtool/frontend/src/components/organisms/RetireAsSoleVoterBox.tsx index b2518b9e4..081e05d40 100644 --- a/govtool/frontend/src/components/organisms/RetireAsSoleVoterBox.tsx +++ b/govtool/frontend/src/components/organisms/RetireAsSoleVoterBox.tsx @@ -6,6 +6,7 @@ import { PATHS } from "@consts"; import { CenteredBoxBottomButtons } from "@molecules"; import { useCardano, useModal } from "@context"; import { RetireAsSoleVoterBoxContent } from "@organisms"; +import { useGetVoterInfo } from "@/hooks"; export const RetireAsSoleVoterBox = () => { const [isLoading, setIsLoading] = useState(false); @@ -18,26 +19,32 @@ export const RetireAsSoleVoterBox = () => { } = useCardano(); const { openModal, closeModal } = useModal(); const { t } = useTranslation(); + const { voter } = useGetVoterInfo(); const onRetire = useCallback(async () => { try { setIsLoading(true); const isPendingTx = isPendingTransaction(); if (isPendingTx) return; - const certBuilder = await buildDRepRetirementCert(); + if (!voter?.deposit) { + throw new Error(t("errors.appCannotGetDeposit")); + } + const certBuilder = await buildDRepRetirementCert( + voter?.deposit?.toString(), + ); const result = await buildSignSubmitConwayCertTx({ certBuilder, - type: "soleVoterRegistration", - registrationType: "retirement", + type: "retireAsSoleVoter", + voterDeposit: voter?.deposit?.toString(), }); - if (result) + if (result) { openModal({ type: "statusModal", state: { status: "success", title: t("modals.retirement.title"), message: t("modals.retirement.message"), - link: `https://adanordic.com/latest_transactions`, + link: "https://adanordic.com/latest_transactions", buttonText: t("modals.common.goToDashboard"), dataTestId: "retirement-transaction-submitted-modal", onSubmit: () => { @@ -46,6 +53,8 @@ export const RetireAsSoleVoterBox = () => { }, }, }); + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (error: any) { const errorMessage = error.info ? error.info : error; @@ -71,6 +80,7 @@ export const RetireAsSoleVoterBox = () => { buildSignSubmitConwayCertTx, isPendingTransaction, openModal, + voter?.deposit, ]); return ( diff --git a/govtool/frontend/src/components/organisms/RetireAsSoleVoterBoxContent.tsx b/govtool/frontend/src/components/organisms/RetireAsSoleVoterBoxContent.tsx index bb371f0c4..776b3b724 100644 --- a/govtool/frontend/src/components/organisms/RetireAsSoleVoterBoxContent.tsx +++ b/govtool/frontend/src/components/organisms/RetireAsSoleVoterBoxContent.tsx @@ -2,14 +2,13 @@ import { Link } from "@mui/material"; import { Trans } from "react-i18next"; import { Typography } from "@atoms"; -import { useScreenDimension, useTranslation } from "@hooks"; -import { correctAdaFormat, openInNewTab } from "@/utils"; -import { useCardano } from "@/context"; +import { useGetVoterInfo, useScreenDimension, useTranslation } from "@hooks"; +import { correctAdaFormat, openInNewTab } from "@utils"; export const RetireAsSoleVoterBoxContent = () => { const { isMobile } = useScreenDimension(); const { t } = useTranslation(); - const { voter } = useCardano(); + const { voter } = useGetVoterInfo(); return ( <> @@ -23,7 +22,6 @@ export const RetireAsSoleVoterBoxContent = () => { mt: isMobile ? 4 : 10, textAlign: "center", whiteSpace: "pre-line", - textDecoration: "underline", }} variant="body1" > @@ -33,7 +31,7 @@ export const RetireAsSoleVoterBoxContent = () => { components={[ openInNewTab("https://sancho.network/")} - sx={{ cursor: "pointer" }} + sx={{ cursor: "pointer", textDecoration: "none" }} key="0" />, ]} diff --git a/govtool/frontend/src/components/organisms/Slider.tsx b/govtool/frontend/src/components/organisms/Slider.tsx index 07bf2cd90..cee7abc27 100644 --- a/govtool/frontend/src/components/organisms/Slider.tsx +++ b/govtool/frontend/src/components/organisms/Slider.tsx @@ -1,16 +1,15 @@ -import { useEffect, useMemo } from "react"; -import { Box, Link, Typography } from "@mui/material"; +import { useCallback, useEffect, useMemo, useState } from "react"; +import { generatePath, useNavigate } from "react-router-dom"; +import { Box } from "@mui/material"; import { KeenSliderOptions } from "keen-slider"; import "keen-slider/keen-slider.min.css"; -import styles from "./slider.module.css"; - -import { ICONS, PATHS } from "@consts"; import { useCardano } from "@context"; -import { useScreenDimension, useSlider, useTranslation } from "@hooks"; -import { generatePath, useNavigate } from "react-router-dom"; - -const SLIDER_MAX_LENGTH = 1000; +import { useScreenDimension, useTranslation, useSlider } from "@hooks"; +import { Button, Typography } from "@atoms"; +import { SliderArrows } from "@molecules"; +import { PATHS } from "@consts"; +import { theme } from "@/theme"; type SliderProps = { title: string; @@ -37,95 +36,121 @@ export const Slider = ({ searchPhrase, sorting, }: SliderProps) => { - const { isMobile, screenWidth, pagePadding } = useScreenDimension(); + const [isSliderInitialized, setIsSliderInitialized] = useState(false); + + const { isMobile, screenWidth } = useScreenDimension(); const navigate = useNavigate(); - const { voteTransaction } = useCardano(); + const { pendingTransaction } = useCardano(); const { t } = useTranslation(); + const { + palette: { primaryBlue, arcticWhite, lightBlue }, + } = theme; + const DEFAULT_SLIDER_CONFIG = { mode: "free", initial: 0, slides: { perView: "auto", - spacing: 24, + spacing: 20, }, } as KeenSliderOptions; - const { - currentRange, - sliderRef, - setPercentageValue, - instanceRef, - setCurrentRange, - } = useSlider({ + const isShowArrows = useMemo( + () => + // Arrows are to be show only on desktop view. + // 268 - side menu width; 40 - distance needed from the left on + // disconnected wallet (no side menu); 350 - gov action card width; + // other values are for paddings and margins + screenWidth < + (onDashboard ? 268 : 40) + 28 + dataLength * 350 + 20 * dataLength - 5, + [screenWidth, dataLength], + ); + + const { sliderRef, instanceRef, currentSlide, itemsPerView } = useSlider({ config: DEFAULT_SLIDER_CONFIG, - sliderMaxLength: SLIDER_MAX_LENGTH, }); - const paddingOffset = useMemo(() => { - const padding = onDashboard ? (isMobile ? 2 : 3.5) : pagePadding; - return padding * 8 * 2; - }, [isMobile, pagePadding]); - const refresh = () => { instanceRef.current?.update(instanceRef.current?.options); - setCurrentRange(0); instanceRef.current?.track.to(0); instanceRef.current?.moveToIdx(0); }; + const onClickShowAll = useCallback(() => { + navigate( + generatePath( + onDashboard + ? PATHS.dashboardGovernanceActionsCategory + : PATHS.governanceActionsCategory, + { + category: navigateKey, + }, + ), + ); + }, [navigate, onDashboard]); + useEffect(() => { - refresh(); - }, [filters, sorting, searchPhrase, voteTransaction?.proposalId, data]); + if (instanceRef.current) { + setIsSliderInitialized(true); + } + }, [instanceRef.current]); - const rangeSliderCalculationElement = - dataLength < notSlicedDataLength - ? (screenWidth + - (onDashboard ? -290 - paddingOffset : -paddingOffset + 250)) / - 437 - : (screenWidth + (onDashboard ? -280 - paddingOffset : -paddingOffset)) / - 402; + useEffect(() => { + refresh(); + }, [ + filters, + sorting, + searchPhrase, + pendingTransaction?.vote?.resourceId, + data, + ]); return ( - - - {title} - - {isMobile && isShowAll && ( - - onDashboard - ? navigate( - generatePath(PATHS.dashboardGovernanceActionsCategory, { - category: navigateKey, - }) - ) - : navigate( - generatePath(PATHS.governanceActionsCategory, { - category: navigateKey, - }) - ) - } - > - + + {title} + {(notSlicedDataLength > 6 || (isMobile && isShowAll)) && ( + + )} + + {isSliderInitialized && isShowArrows && dataLength > 1 && !isMobile && ( + )}
    {data} - {!isMobile && isShowAll && dataLength < notSlicedDataLength && ( -
    - - onDashboard - ? navigate( - generatePath(PATHS.dashboardGovernanceActionsCategory, { - category: navigateKey, - }) - ) - : navigate( - generatePath(PATHS.governanceActionsCategory, { - category: navigateKey, - }) - ) - } - > - - {t("slider.viewAll")} - - arrow - -
    - )}
    - {!isMobile && Math.floor(rangeSliderCalculationElement) < dataLength && ( - - - - )}
    ); }; diff --git a/govtool/frontend/src/components/organisms/StatusModal.tsx b/govtool/frontend/src/components/organisms/StatusModal.tsx index 0a6ec6c66..ce8f2665d 100644 --- a/govtool/frontend/src/components/organisms/StatusModal.tsx +++ b/govtool/frontend/src/components/organisms/StatusModal.tsx @@ -22,7 +22,7 @@ export interface StatusModalState { dataTestId: string; } -export function StatusModal() { +export const StatusModal = () => { const { state, closeModal } = useModal(); const { isMobile } = useScreenDimension(); const { t } = useTranslation(); @@ -48,7 +48,7 @@ export function StatusModal() { textAlign="center" sx={{ fontSize: "16px", fontWeight: "400" }} > - {state?.message} + {state?.message}{" "} {state?.link && ( openInNewTab(state?.link || "")} @@ -111,4 +111,4 @@ export function StatusModal() { )} ); -} +}; diff --git a/govtool/frontend/src/components/organisms/TopNav.tsx b/govtool/frontend/src/components/organisms/TopNav.tsx index 3da53b62f..ecfed9932 100644 --- a/govtool/frontend/src/components/organisms/TopNav.tsx +++ b/govtool/frontend/src/components/organisms/TopNav.tsx @@ -78,7 +78,11 @@ export const TopNav = ({ isConnectButton = true }) => { onClick={() => (isConnectButton ? {} : disconnectWallet())} to={PATHS.home} > - + app-logo {screenWidth >= 1024 ? (