From da8854d6e1185834a4a837217fbaf17a85c560d4 Mon Sep 17 00:00:00 2001 From: gratestas Date: Mon, 9 Jan 2023 14:55:26 +0300 Subject: [PATCH 001/132] chore: install packages --- package.json | 6 + yarn.lock | 479 ++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 480 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index f4096b44..6ba04ec9 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,9 @@ "deep-equal": "^2.0.5", "deepmerge": "^4.2.2", "did-resolver": "^2.1.2", + "ethers": "^5.7.2", + "graphql": "^16.6.0", + "graphql-request": "^5.1.0", "history": "^4.10.1", "hoist-non-react-statics": "^3.3.2", "immer": "^9.0.6", @@ -64,6 +67,7 @@ "js-base64": "^3.6.0", "js-combinatorics": "^0.6.1", "localforage": "^1.10.0", + "moment": "^2.29.4", "nanoid": "^3.1.9", "normalize.css": "^8.0.1", "path-to-regexp": "^6.1.0", @@ -77,6 +81,7 @@ "react-router": "^5.1.2", "react-router-dom": "^5.1.2", "react-titled": "^1.0.1", + "react-toastify": "^9.1.1", "react-visibility-sensor": "^5.1.1", "redux": "^4.1.1", "redux-persist": "^6.0.0", @@ -85,6 +90,7 @@ "scroll-into-view-if-needed": "^2.2.25", "serialize-error": "^7.0.1", "styled-components": "^5.3.1", + "swr": "^2.0.0", "web3": "^1.5.2" }, "devDependencies": { diff --git a/yarn.lock b/yarn.lock index 8b73ce71..ea94f1d1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2142,6 +2142,21 @@ "@ethersproject/properties" "^5.0.3" "@ethersproject/strings" "^5.0.4" +"@ethersproject/abi@5.7.0", "@ethersproject/abi@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.7.0.tgz#b3f3e045bbbeed1af3947335c247ad625a44e449" + integrity sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA== + dependencies: + "@ethersproject/address" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/hash" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + "@ethersproject/abi@>=5.0.0-beta.153": version "5.4.0" resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.4.0.tgz#a6d63bdb3672f738398846d4279fa6b6c9818242" @@ -2170,6 +2185,19 @@ "@ethersproject/transactions" "^5.0.5" "@ethersproject/web" "^5.0.6" +"@ethersproject/abstract-provider@5.7.0", "@ethersproject/abstract-provider@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/abstract-provider/-/abstract-provider-5.7.0.tgz#b0a8550f88b6bf9d51f90e4795d48294630cb9ef" + integrity sha512-R41c9UkchKCpAqStMYUpdunjo3pkEvZC3FAwZn5S5MGbXoMQOHIdHItezTETxAO5bevtMApSyEhn9+CHcDsWBw== + dependencies: + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/networks" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + "@ethersproject/web" "^5.7.0" + "@ethersproject/abstract-provider@>=5.0.0-beta.131", "@ethersproject/abstract-provider@>=5.0.0-beta.139", "@ethersproject/abstract-provider@^5.4.0": version "5.4.1" resolved "https://registry.yarnpkg.com/@ethersproject/abstract-provider/-/abstract-provider-5.4.1.tgz#e404309a29f771bd4d28dbafadcaa184668c2a6e" @@ -2207,6 +2235,17 @@ "@ethersproject/logger" "^5.0.5" "@ethersproject/properties" "^5.0.3" +"@ethersproject/abstract-signer@5.7.0", "@ethersproject/abstract-signer@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/abstract-signer/-/abstract-signer-5.7.0.tgz#13f4f32117868452191a4649723cb086d2b596b2" + integrity sha512-a16V8bq1/Cz+TGCkE2OPMTOUDLS3grCpdjoJCYNnVBbdYEMSgKrU0+B90s8b6H+ByYTBZN7a3g76jdIJi7UfKQ== + dependencies: + "@ethersproject/abstract-provider" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/abstract-signer@>=5.0.0-beta.132", "@ethersproject/abstract-signer@>=5.0.0-beta.142", "@ethersproject/abstract-signer@^5.4.0": version "5.4.1" resolved "https://registry.yarnpkg.com/@ethersproject/abstract-signer/-/abstract-signer-5.4.1.tgz#e4e9abcf4dd4f1ba0db7dff9746a5f78f355ea81" @@ -2240,6 +2279,17 @@ "@ethersproject/logger" "^5.0.5" "@ethersproject/rlp" "^5.0.3" +"@ethersproject/address@5.7.0", "@ethersproject/address@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/address/-/address-5.7.0.tgz#19b56c4d74a3b0a46bfdbb6cfcc0a153fc697f37" + integrity sha512-9wYhYt7aghVGo758POM5nqcOMaE168Q6aRLJZwUmiqSrAungkG74gSSeKEIR7ukixesdRZGPgVqme6vmxs1fkA== + dependencies: + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/rlp" "^5.7.0" + "@ethersproject/address@>=5.0.0-beta.128", "@ethersproject/address@>=5.0.0-beta.134", "@ethersproject/address@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@ethersproject/address/-/address-5.4.0.tgz#ba2d00a0f8c4c0854933b963b9a3a9f6eb4a37a3" @@ -2270,6 +2320,13 @@ dependencies: "@ethersproject/bytes" "^5.0.4" +"@ethersproject/base64@5.7.0", "@ethersproject/base64@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/base64/-/base64-5.7.0.tgz#ac4ee92aa36c1628173e221d0d01f53692059e1c" + integrity sha512-Dr8tcHt2mEbsZr/mwTPIQAf3Ai0Bks/7gTw9dSqk1mQvhW3XvRlmDJr/4n+wg1JmCl16NZue17CDh8xb/vZ0sQ== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/base64@^5.0.3": version "5.0.4" resolved "https://registry.yarnpkg.com/@ethersproject/base64/-/base64-5.0.4.tgz#b0d8fdbf3dda977cf546dcd35725a7b1d5256caa" @@ -2292,6 +2349,14 @@ "@ethersproject/bytes" "^5.0.4" "@ethersproject/properties" "^5.0.3" +"@ethersproject/basex@5.7.0", "@ethersproject/basex@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/basex/-/basex-5.7.0.tgz#97034dc7e8938a8ca943ab20f8a5e492ece4020b" + integrity sha512-ywlh43GwZLv2Voc2gQVTKBoVQ1mti3d8HK5aMxsfu/nRDnMmNqaSJ3r3n85HBByT8OpoY96SXM1FogC533T4zw== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/basex@>=5.0.0-beta.127", "@ethersproject/basex@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@ethersproject/basex/-/basex-5.4.0.tgz#0a2da0f4e76c504a94f2b21d3161ed9438c7f8a6" @@ -2317,6 +2382,15 @@ "@ethersproject/logger" "^5.0.5" bn.js "^4.4.0" +"@ethersproject/bignumber@5.7.0", "@ethersproject/bignumber@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/bignumber/-/bignumber-5.7.0.tgz#e2f03837f268ba655ffba03a57853e18a18dc9c2" + integrity sha512-n1CAdIHRWjSucQO3MC1zPSVgV/6dy/fjL9pMrPP9peL+QxEg9wOsVqwD4+818B6LUEtaXzVHQiuivzRoxPxUGw== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + bn.js "^5.2.1" + "@ethersproject/bignumber@>=5.0.0-beta.130", "@ethersproject/bignumber@>=5.0.0-beta.138", "@ethersproject/bignumber@^5.4.0": version "5.4.1" resolved "https://registry.yarnpkg.com/@ethersproject/bignumber/-/bignumber-5.4.1.tgz#64399d3b9ae80aa83d483e550ba57ea062c1042d" @@ -2342,6 +2416,13 @@ dependencies: "@ethersproject/logger" "^5.0.5" +"@ethersproject/bytes@5.7.0", "@ethersproject/bytes@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/bytes/-/bytes-5.7.0.tgz#a00f6ea8d7e7534d6d87f47188af1148d71f155d" + integrity sha512-nsbxwgFXWh9NyYWo+U8atvmMsSdKJprTcICAkvbBffT75qDocbuggBU0SJiVK2MuTrp0q+xvLkTnGMPK1+uA9A== + dependencies: + "@ethersproject/logger" "^5.7.0" + "@ethersproject/bytes@>=5.0.0-beta.129", "@ethersproject/bytes@>=5.0.0-beta.137", "@ethersproject/bytes@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@ethersproject/bytes/-/bytes-5.4.0.tgz#56fa32ce3bf67153756dbaefda921d1d4774404e" @@ -2363,6 +2444,13 @@ dependencies: "@ethersproject/bignumber" "^5.0.7" +"@ethersproject/constants@5.7.0", "@ethersproject/constants@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/constants/-/constants-5.7.0.tgz#df80a9705a7e08984161f09014ea012d1c75295e" + integrity sha512-DHI+y5dBNvkpYUMiRQyxRBYBefZkJfo70VUkUAsRjcPs47muV9evftfZ0PJVCXYbAiCgght0DtcF9srFQmIgWA== + dependencies: + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/constants@>=5.0.0-beta.128", "@ethersproject/constants@>=5.0.0-beta.133", "@ethersproject/constants@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@ethersproject/constants/-/constants-5.4.0.tgz#ee0bdcb30bf1b532d2353c977bf2ef1ee117958a" @@ -2408,6 +2496,22 @@ "@ethersproject/logger" "^5.0.5" "@ethersproject/properties" "^5.0.3" +"@ethersproject/contracts@5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/contracts/-/contracts-5.7.0.tgz#c305e775abd07e48aa590e1a877ed5c316f8bd1e" + integrity sha512-5GJbzEU3X+d33CdfPhcyS+z8MzsTrBGk/sc+G+59+tPa9yFkl6HQ9D6L0QMgNTA9q8dT0XKxxkyp883XsQvbbg== + dependencies: + "@ethersproject/abi" "^5.7.0" + "@ethersproject/abstract-provider" "^5.7.0" + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/address" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + "@ethersproject/contracts@^5.0.5": version "5.0.5" resolved "https://registry.yarnpkg.com/@ethersproject/contracts/-/contracts-5.0.5.tgz#64831a341ec8ca225e83ff3e9437c26b970fd5d7" @@ -2437,6 +2541,21 @@ "@ethersproject/properties" "^5.0.4" "@ethersproject/strings" "^5.0.4" +"@ethersproject/hash@5.7.0", "@ethersproject/hash@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/hash/-/hash-5.7.0.tgz#eb7aca84a588508369562e16e514b539ba5240a7" + integrity sha512-qX5WrQfnah1EFnO5zJv1v46a8HW0+E5xuBBDTwMFZLuVTx0tbU2kkx15NqdjxecrLGatQN9FGQKpb1FKdHCt+g== + dependencies: + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/address" "^5.7.0" + "@ethersproject/base64" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + "@ethersproject/hash@>=5.0.0-beta.128", "@ethersproject/hash@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@ethersproject/hash/-/hash-5.4.0.tgz#d18a8e927e828e22860a011f39e429d388344ae0" @@ -2501,6 +2620,24 @@ "@ethersproject/transactions" "^5.0.5" "@ethersproject/wordlists" "^5.0.4" +"@ethersproject/hdnode@5.7.0", "@ethersproject/hdnode@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/hdnode/-/hdnode-5.7.0.tgz#e627ddc6b466bc77aebf1a6b9e47405ca5aef9cf" + integrity sha512-OmyYo9EENBPPf4ERhR7oj6uAtUAhYGqOnIS+jE5pTXvdKBS99ikzq1E7Iv0ZQZ5V36Lqx1qZLeak0Ra16qpeOg== + dependencies: + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/basex" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/pbkdf2" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/sha2" "^5.7.0" + "@ethersproject/signing-key" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + "@ethersproject/wordlists" "^5.7.0" + "@ethersproject/hdnode@>=5.0.0-beta.130", "@ethersproject/hdnode@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@ethersproject/hdnode/-/hdnode-5.4.0.tgz#4bc9999b9a12eb5ce80c5faa83114a57e4107cac" @@ -2538,6 +2675,25 @@ aes-js "3.0.0" scrypt-js "3.0.1" +"@ethersproject/json-wallets@5.7.0", "@ethersproject/json-wallets@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/json-wallets/-/json-wallets-5.7.0.tgz#5e3355287b548c32b368d91014919ebebddd5360" + integrity sha512-8oee5Xgu6+RKgJTkvEMl2wDgSPSAQ9MB/3JYjFV9jlKvcYHUXZC+cQp0njgmxdHkYWn8s6/IqIZYm0YWCjO/0g== + dependencies: + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/address" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/hdnode" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/pbkdf2" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/random" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + aes-js "3.0.0" + scrypt-js "3.0.1" + "@ethersproject/json-wallets@>=5.0.0-beta.129": version "5.4.0" resolved "https://registry.yarnpkg.com/@ethersproject/json-wallets/-/json-wallets-5.4.0.tgz#2583341cfe313fc9856642e8ace3080154145e95" @@ -2565,6 +2721,14 @@ "@ethersproject/bytes" "^5.0.4" js-sha3 "0.5.7" +"@ethersproject/keccak256@5.7.0", "@ethersproject/keccak256@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/keccak256/-/keccak256-5.7.0.tgz#3186350c6e1cd6aba7940384ec7d6d9db01f335a" + integrity sha512-2UcPboeL/iW+pSg6vZ6ydF8tCnv3Iu/8tUmLLzWWGzxWKFFqOBQFLo6uLUv6BDrLgCDfN28RJ/wtByx+jZ4KBg== + dependencies: + "@ethersproject/bytes" "^5.7.0" + js-sha3 "0.8.0" + "@ethersproject/keccak256@>=5.0.0-beta.127", "@ethersproject/keccak256@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@ethersproject/keccak256/-/keccak256-5.4.0.tgz#7143b8eea4976080241d2bd92e3b1f1bf7025318" @@ -2586,6 +2750,11 @@ resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.0.8.tgz#135c1903d35c878265f3cbf2b287042c4c20d5d4" integrity sha512-SkJCTaVTnaZ3/ieLF5pVftxGEFX56pTH+f2Slrpv7cU0TNpUZNib84QQdukd++sWUp/S7j5t5NW+WegbXd4U/A== +"@ethersproject/logger@5.7.0", "@ethersproject/logger@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.7.0.tgz#6ce9ae168e74fecf287be17062b590852c311892" + integrity sha512-0odtFdXu/XHtjQXJYA3u9G0G8btm0ND5Cu8M7i5vhEcE8/HmF4Lbdqanwyv4uQTr2tx6b7fQRmgLrsnpQlmnig== + "@ethersproject/logger@>=5.0.0-beta.129", "@ethersproject/logger@>=5.0.0-beta.137", "@ethersproject/logger@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.4.0.tgz#f39adadf62ad610c420bcd156fd41270e91b3ca9" @@ -2603,6 +2772,13 @@ dependencies: "@ethersproject/logger" "^5.0.5" +"@ethersproject/networks@5.7.1", "@ethersproject/networks@^5.7.0": + version "5.7.1" + resolved "https://registry.yarnpkg.com/@ethersproject/networks/-/networks-5.7.1.tgz#118e1a981d757d45ccea6bb58d9fd3d9db14ead6" + integrity sha512-n/MufjFYv3yFcUyfhnXotyDlNdFb7onmkSy8aQERi2PjNcnWQ66xXxa3XlS8nCcA8aJKJjIIMNJTC7tu80GwpQ== + dependencies: + "@ethersproject/logger" "^5.7.0" + "@ethersproject/networks@>=5.0.0-beta.129", "@ethersproject/networks@^5.4.0": version "5.4.2" resolved "https://registry.yarnpkg.com/@ethersproject/networks/-/networks-5.4.2.tgz#2247d977626e97e2c3b8ee73cd2457babde0ce35" @@ -2625,6 +2801,14 @@ "@ethersproject/bytes" "^5.0.4" "@ethersproject/sha2" "^5.0.3" +"@ethersproject/pbkdf2@5.7.0", "@ethersproject/pbkdf2@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/pbkdf2/-/pbkdf2-5.7.0.tgz#d2267d0a1f6e123f3771007338c47cccd83d3102" + integrity sha512-oR/dBRZR6GTyaofd86DehG72hY6NpAjhabkhxgr3X2FpJtJuodEl2auADWBZfhDHgVCbu3/H/Ocq2uC6dpNjjw== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/sha2" "^5.7.0" + "@ethersproject/pbkdf2@>=5.0.0-beta.127", "@ethersproject/pbkdf2@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@ethersproject/pbkdf2/-/pbkdf2-5.4.0.tgz#ed88782a67fda1594c22d60d0ca911a9d669641c" @@ -2640,6 +2824,13 @@ dependencies: "@ethersproject/logger" "^5.0.5" +"@ethersproject/properties@5.7.0", "@ethersproject/properties@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/properties/-/properties-5.7.0.tgz#a6e12cb0439b878aaf470f1902a176033067ed30" + integrity sha512-J87jy8suntrAkIZtecpxEPxY//szqr1mlBaYlQ0r4RCaiD2hjheqF9s1LVE8vVuJCXisjIP+JgtK/Do54ej4Sw== + dependencies: + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties@>=5.0.0-beta.131", "@ethersproject/properties@>=5.0.0-beta.140", "@ethersproject/properties@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@ethersproject/properties/-/properties-5.4.0.tgz#38ba20539b44dcc5d5f80c45ad902017dcdbefe7" @@ -2700,6 +2891,32 @@ bech32 "1.1.4" ws "7.2.3" +"@ethersproject/providers@5.7.2": + version "5.7.2" + resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.7.2.tgz#f8b1a4f275d7ce58cf0a2eec222269a08beb18cb" + integrity sha512-g34EWZ1WWAVgr4aptGlVBF8mhl3VWjv+8hoAnzStu8Ah22VHBsuGzP17eb6xDVRzw895G4W7vvx60lFFur/1Rg== + dependencies: + "@ethersproject/abstract-provider" "^5.7.0" + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/address" "^5.7.0" + "@ethersproject/base64" "^5.7.0" + "@ethersproject/basex" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/hash" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/networks" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/random" "^5.7.0" + "@ethersproject/rlp" "^5.7.0" + "@ethersproject/sha2" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + "@ethersproject/web" "^5.7.0" + bech32 "1.1.4" + ws "7.4.6" + "@ethersproject/providers@^5.0.14": version "5.0.14" resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.0.14.tgz#751ccb14b4a8c8e9e4be171818c23f4601be90ba" @@ -2733,6 +2950,14 @@ "@ethersproject/bytes" "^5.0.4" "@ethersproject/logger" "^5.0.5" +"@ethersproject/random@5.7.0", "@ethersproject/random@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/random/-/random-5.7.0.tgz#af19dcbc2484aae078bb03656ec05df66253280c" + integrity sha512-19WjScqRA8IIeWclFme75VMXSBvi4e6InrUNuaR4s5pTF2qNhcGdCUwdxUVGtDDqC00sDLCO93jPQoDUH4HVmQ== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/random@>=5.0.0-beta.128", "@ethersproject/random@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@ethersproject/random/-/random-5.4.0.tgz#9cdde60e160d024be39cc16f8de3b9ce39191e16" @@ -2757,6 +2982,14 @@ "@ethersproject/bytes" "^5.0.4" "@ethersproject/logger" "^5.0.5" +"@ethersproject/rlp@5.7.0", "@ethersproject/rlp@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/rlp/-/rlp-5.7.0.tgz#de39e4d5918b9d74d46de93af80b7685a9c21304" + integrity sha512-rBxzX2vK8mVF7b0Tol44t5Tb8gomOHkj5guL+HhzQ1yBh/ydjGnpw6at+X6Iw0Kp3OzzzkcKp8N9r0W4kYSs9w== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/rlp@>=5.0.0-beta.126", "@ethersproject/rlp@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@ethersproject/rlp/-/rlp-5.4.0.tgz#de61afda5ff979454e76d3b3310a6c32ad060931" @@ -2782,6 +3015,15 @@ "@ethersproject/logger" "^5.0.5" hash.js "1.1.3" +"@ethersproject/sha2@5.7.0", "@ethersproject/sha2@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/sha2/-/sha2-5.7.0.tgz#9a5f7a7824ef784f7f7680984e593a800480c9fb" + integrity sha512-gKlH42riwb3KYp0reLsFTokByAKoJdgFCwI+CCiX/k+Jm2mbNs6oOaCjYQSlI1+XBVejwH2KrmCbMAT/GnRDQw== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + hash.js "1.1.7" + "@ethersproject/sha2@>=5.0.0-beta.129", "@ethersproject/sha2@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@ethersproject/sha2/-/sha2-5.4.0.tgz#c9a8db1037014cbc4e9482bd662f86c090440371" @@ -2810,6 +3052,18 @@ "@ethersproject/properties" "^5.0.3" elliptic "6.5.3" +"@ethersproject/signing-key@5.7.0", "@ethersproject/signing-key@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/signing-key/-/signing-key-5.7.0.tgz#06b2df39411b00bc57c7c09b01d1e41cf1b16ab3" + integrity sha512-MZdy2nL3wO0u7gkB4nA/pEf8lu1TlFswPNmy8AiYkfKTdO6eXBJyUdmHO/ehm/htHw9K/qF8ujnTyUAD+Ry54Q== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + bn.js "^5.2.1" + elliptic "6.5.4" + hash.js "1.1.7" + "@ethersproject/signing-key@>=5.0.0-beta.129", "@ethersproject/signing-key@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@ethersproject/signing-key/-/signing-key-5.4.0.tgz#2f05120984e81cf89a3d5f6dec5c68ee0894fbec" @@ -2843,6 +3097,18 @@ "@ethersproject/sha2" "^5.0.3" "@ethersproject/strings" "^5.0.4" +"@ethersproject/solidity@5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/solidity/-/solidity-5.7.0.tgz#5e9c911d8a2acce2a5ebb48a5e2e0af20b631cb8" + integrity sha512-HmabMd2Dt/raavyaGukF4XxizWKhKQ24DoLtdNbBmNKUOPqwjsKQSdV9GQtj9CBEea9DlzETlVER1gYeXXBGaA== + dependencies: + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/sha2" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + "@ethersproject/solidity@^5.0.5": version "5.0.5" resolved "https://registry.yarnpkg.com/@ethersproject/solidity/-/solidity-5.0.5.tgz#97a7d8a67f2d944f208c948fed0d565512bcc2be" @@ -2863,6 +3129,15 @@ "@ethersproject/constants" "^5.0.4" "@ethersproject/logger" "^5.0.5" +"@ethersproject/strings@5.7.0", "@ethersproject/strings@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/strings/-/strings-5.7.0.tgz#54c9d2a7c57ae8f1205c88a9d3a56471e14d5ed2" + integrity sha512-/9nu+lj0YswRNSH0NXYqrh8775XNyEdUQAuf3f+SmOrnVewcJ5SBNAjF7lpgehKi4abvNNXyf+HX86czCdJ8Mg== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/strings@>=5.0.0-beta.130", "@ethersproject/strings@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@ethersproject/strings/-/strings-5.4.0.tgz#fb12270132dd84b02906a8d895ae7e7fa3d07d9a" @@ -2896,6 +3171,21 @@ "@ethersproject/rlp" "^5.0.3" "@ethersproject/signing-key" "^5.0.4" +"@ethersproject/transactions@5.7.0", "@ethersproject/transactions@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/transactions/-/transactions-5.7.0.tgz#91318fc24063e057885a6af13fdb703e1f993d3b" + integrity sha512-kmcNicCp1lp8qanMTC3RIikGgoJ80ztTyvtsFvCYpSCfkjhD0jZ2LOrnbcuxuToLIUYYf+4XwD1rP+B/erDIhQ== + dependencies: + "@ethersproject/address" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/rlp" "^5.7.0" + "@ethersproject/signing-key" "^5.7.0" + "@ethersproject/transactions@>=5.0.0-beta.128", "@ethersproject/transactions@>=5.0.0-beta.135", "@ethersproject/transactions@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@ethersproject/transactions/-/transactions-5.4.0.tgz#a159d035179334bd92f340ce0f77e83e9e1522e0" @@ -2935,6 +3225,15 @@ "@ethersproject/constants" "^5.0.4" "@ethersproject/logger" "^5.0.5" +"@ethersproject/units@5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/units/-/units-5.7.0.tgz#637b563d7e14f42deeee39245275d477aae1d8b1" + integrity sha512-pD3xLMy3SJu9kG5xDGI7+xhTEmGXlEqXU4OfNapmfnxLVY4EMSSRp7j1k7eezutBPH7RBN/7QPnwR7hzNlEFeg== + dependencies: + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/wallet@5.0.0-beta.136": version "5.0.0-beta.136" resolved "https://registry.yarnpkg.com/@ethersproject/wallet/-/wallet-5.0.0-beta.136.tgz#8acf7639df645131e219b78380970d21db2198e5" @@ -2977,6 +3276,27 @@ "@ethersproject/transactions" "^5.0.5" "@ethersproject/wordlists" "^5.0.4" +"@ethersproject/wallet@5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/wallet/-/wallet-5.7.0.tgz#4e5d0790d96fe21d61d38fb40324e6c7ef350b2d" + integrity sha512-MhmXlJXEJFBFVKrDLB4ZdDzxcBxQ3rLyCkhNqVu3CDYvR97E+8r01UgrI+TI99Le+aYm/in/0vp86guJuM7FCA== + dependencies: + "@ethersproject/abstract-provider" "^5.7.0" + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/address" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/hash" "^5.7.0" + "@ethersproject/hdnode" "^5.7.0" + "@ethersproject/json-wallets" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/random" "^5.7.0" + "@ethersproject/signing-key" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + "@ethersproject/wordlists" "^5.7.0" + "@ethersproject/web@5.0.11": version "5.0.11" resolved "https://registry.yarnpkg.com/@ethersproject/web/-/web-5.0.11.tgz#d47da612b958b4439e415782a53c8f8461522d68" @@ -2988,6 +3308,17 @@ "@ethersproject/properties" "^5.0.3" "@ethersproject/strings" "^5.0.4" +"@ethersproject/web@5.7.1", "@ethersproject/web@^5.7.0": + version "5.7.1" + resolved "https://registry.yarnpkg.com/@ethersproject/web/-/web-5.7.1.tgz#de1f285b373149bee5928f4eb7bcb87ee5fbb4ae" + integrity sha512-Gueu8lSvyjBWL4cYsWsjh6MtMwM0+H4HvqFPZfB6dV8ctbP9zFAO73VG1cMWae0FLPCtz0peKPpZY8/ugJJX2w== + dependencies: + "@ethersproject/base64" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + "@ethersproject/web@>=5.0.0-beta.129", "@ethersproject/web@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@ethersproject/web/-/web-5.4.0.tgz#49fac173b96992334ed36a175538ba07a7413d1f" @@ -3021,6 +3352,17 @@ "@ethersproject/properties" "^5.0.3" "@ethersproject/strings" "^5.0.4" +"@ethersproject/wordlists@5.7.0", "@ethersproject/wordlists@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/wordlists/-/wordlists-5.7.0.tgz#8fb2c07185d68c3e09eb3bfd6e779ba2774627f5" + integrity sha512-S2TFNJNfHWVHNE6cNDjbVlZ6MgE17MIxMbMg2zv3wn+3XSJGosL1m9ZVv3GXCf/2ymSsQ+hRI5IzoMJTG6aoVA== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/hash" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + "@ethersproject/wordlists@>=5.0.0-beta.128", "@ethersproject/wordlists@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@ethersproject/wordlists/-/wordlists-5.4.0.tgz#f34205ec3bbc9e2c49cadaee774cf0b07e7573d7" @@ -3032,6 +3374,11 @@ "@ethersproject/properties" "^5.4.0" "@ethersproject/strings" "^5.4.0" +"@graphql-typed-document-node/core@^3.1.1": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@graphql-typed-document-node/core/-/core-3.1.1.tgz#076d78ce99822258cf813ecc1e7fa460fa74d052" + integrity sha512-NQ17ii0rK1b34VZonlmT2QMJFI70m0TRwbknO/ihlbatXyaktDhN/98vBiUU6kNBPljqGqyIrl2T4nY2RpFANg== + "@hapi/accept@^3.2.4": version "3.2.4" resolved "https://registry.yarnpkg.com/@hapi/accept/-/accept-3.2.4.tgz#687510529493fe1d7d47954c31aff360d9364bd1" @@ -6055,6 +6402,11 @@ bn.js@^5.1.2: resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.0.tgz#358860674396c6997771a9d051fcc1b57d4ae002" integrity sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw== +bn.js@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" + integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== + body-parser@1.19.0, body-parser@^1.16.0: version "1.19.0" resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" @@ -6798,6 +7150,11 @@ clsx@^1.1.0: resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.0.tgz#62937c6adfea771247c34b54d320fb99624f5702" integrity sha512-3avwM37fSK5oP6M5rQ9CNe99lwxhXDOeSWVPAOYF6OazUTgZCMb0yWlJpmdD74REy1gkEaFiub2ULv4fq9GUhA== +clsx@^1.1.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12" + integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg== + coa@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/coa/-/coa-2.0.2.tgz#43f6c21151b4ef2bf57187db0d73de229e3e7ec3" @@ -7196,6 +7553,13 @@ cross-fetch@^2.1.0, cross-fetch@^2.1.1: node-fetch "2.6.1" whatwg-fetch "2.0.4" +cross-fetch@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.5.tgz#e1389f44d9e7ba767907f7af8454787952ab534f" + integrity sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw== + dependencies: + node-fetch "2.6.7" + cross-spawn@^5.0.1: version "5.1.0" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" @@ -9283,6 +9647,42 @@ ethers@^5.0.8: "@ethersproject/web" "5.0.11" "@ethersproject/wordlists" "5.0.7" +ethers@^5.7.2: + version "5.7.2" + resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.7.2.tgz#3a7deeabbb8c030d4126b24f84e525466145872e" + integrity sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg== + dependencies: + "@ethersproject/abi" "5.7.0" + "@ethersproject/abstract-provider" "5.7.0" + "@ethersproject/abstract-signer" "5.7.0" + "@ethersproject/address" "5.7.0" + "@ethersproject/base64" "5.7.0" + "@ethersproject/basex" "5.7.0" + "@ethersproject/bignumber" "5.7.0" + "@ethersproject/bytes" "5.7.0" + "@ethersproject/constants" "5.7.0" + "@ethersproject/contracts" "5.7.0" + "@ethersproject/hash" "5.7.0" + "@ethersproject/hdnode" "5.7.0" + "@ethersproject/json-wallets" "5.7.0" + "@ethersproject/keccak256" "5.7.0" + "@ethersproject/logger" "5.7.0" + "@ethersproject/networks" "5.7.1" + "@ethersproject/pbkdf2" "5.7.0" + "@ethersproject/properties" "5.7.0" + "@ethersproject/providers" "5.7.2" + "@ethersproject/random" "5.7.0" + "@ethersproject/rlp" "5.7.0" + "@ethersproject/sha2" "5.7.0" + "@ethersproject/signing-key" "5.7.0" + "@ethersproject/solidity" "5.7.0" + "@ethersproject/strings" "5.7.0" + "@ethersproject/transactions" "5.7.0" + "@ethersproject/units" "5.7.0" + "@ethersproject/wallet" "5.7.0" + "@ethersproject/web" "5.7.1" + "@ethersproject/wordlists" "5.7.0" + ethjs-unit@0.1.6: version "0.1.6" resolved "https://registry.yarnpkg.com/ethjs-unit/-/ethjs-unit-0.1.6.tgz#c665921e476e87bce2a9d588a6fe0405b2c41699" @@ -9472,6 +9872,11 @@ extglob@^2.0.4: snapdragon "^0.8.1" to-regex "^3.0.1" +extract-files@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/extract-files/-/extract-files-9.0.0.tgz#8a7744f2437f81f5ed3250ed9f1550de902fe54a" + integrity sha512-CvdFfHkC95B4bBBk36hcEmvdR2awOdhhVUYH6S/zrVj3477zven/fJMYg7121h4T1xHZC+tetUpubpAhxwI7hQ== + extsprintf@1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" @@ -10304,6 +10709,21 @@ graphql-request@^1.8.2: dependencies: cross-fetch "2.2.2" +graphql-request@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/graphql-request/-/graphql-request-5.1.0.tgz#dbc8feee27d21b993cd5da2d3af67821827b240a" + integrity sha512-0OeRVYigVwIiXhNmqnPDt+JhMzsjinxHE7TVy3Lm6jUzav0guVcL0lfSbi6jVTRAxcbwgyr6yrZioSHxf9gHzw== + dependencies: + "@graphql-typed-document-node/core" "^3.1.1" + cross-fetch "^3.1.5" + extract-files "^9.0.0" + form-data "^3.0.0" + +graphql@^16.6.0: + version "16.6.0" + resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.6.0.tgz#c2dcffa4649db149f6282af726c8c83f1c7c5fdb" + integrity sha512-KPIBPDlW7NxrbT/eh4qPXz5FiFdL5UbaA0XUNz2Rp3Z3hqBSkbj0GVjwFDztsWVauZUWsbKHgMg++sk8UX0bkw== + hamt-sharding@~0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/hamt-sharding/-/hamt-sharding-0.0.2.tgz#53691f72122f1929a92a4688c7bb59545a8998ac" @@ -14558,6 +14978,11 @@ moment@^2.25.3: resolved "https://registry.yarnpkg.com/moment/-/moment-2.27.0.tgz#8bff4e3e26a236220dfe3e36de756b6ebaa0105d" integrity sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ== +moment@^2.29.4: + version "2.29.4" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.4.tgz#3dbe052889fe7c1b2ed966fcb3a77328964ef108" + integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w== + mortice@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/mortice/-/mortice-2.0.0.tgz#7be171409c2115561ba3fc035e4527f9082eefde" @@ -15042,6 +15467,13 @@ node-fetch@2.6.1, node-fetch@^2.3.0, node-fetch@^2.6.1: resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== +node-fetch@2.6.7: + version "2.6.7" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" + integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== + dependencies: + whatwg-url "^5.0.0" + node-fetch@^2.6.0: version "2.6.0" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd" @@ -18278,6 +18710,13 @@ react-titled@^1.0.1: dependencies: prop-types "^15.5.8" +react-toastify@^9.1.1: + version "9.1.1" + resolved "https://registry.yarnpkg.com/react-toastify/-/react-toastify-9.1.1.tgz#9280caea4a13dc1739c350d90660a630807bf10b" + integrity sha512-pkFCla1z3ve045qvjEmn2xOJOy4ZciwRXm1oMPULVkELi5aJdHCN/FHnuqXq8IwGDLB7PPk2/J6uP9D8ejuiRw== + dependencies: + clsx "^1.1.1" + react-visibility-sensor@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/react-visibility-sensor/-/react-visibility-sensor-5.1.1.tgz#5238380960d3a0b2be0b7faddff38541e337f5a9" @@ -20276,6 +20715,13 @@ swarm-js@^0.1.40: tar "^4.0.2" xhr-request "^1.0.1" +swr@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/swr/-/swr-2.0.0.tgz#91d999359e2be92de1a41f6b6711d72be20ffdbd" + integrity sha512-IhUx5yPkX+Fut3h0SqZycnaNLXLXsb2ECFq0Y29cxnK7d8r7auY2JWNbCW3IX+EqXUg3rwNJFlhrw5Ye/b6k7w== + dependencies: + use-sync-external-store "^1.2.0" + symbol-observable@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" @@ -20618,6 +21064,11 @@ tr46@^1.0.1: dependencies: punycode "^2.1.0" +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== + trim-newlines@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-2.0.0.tgz#b403d0b91be50c331dfc4b82eeceb22c3de16d20" @@ -21126,6 +21577,11 @@ ursa-optional@~0.10.0: bindings "^1.5.0" nan "^2.14.2" +use-sync-external-store@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a" + integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA== + use@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" @@ -21924,6 +22380,11 @@ webcrypto@~0.1.1: crypto-browserify "^3.10.0" detect-node "^2.0.3" +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== + webidl-conversions@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" @@ -21962,6 +22423,14 @@ whatwg-mimetype@^2.2.0, whatwg-mimetype@^2.3.0: resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + whatwg-url@^7.0.0: version "7.1.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-7.1.0.tgz#c2c492f1eca612988efd3d2266be1b9fc6170d06" @@ -22124,6 +22593,11 @@ ws@7.2.3: resolved "https://registry.yarnpkg.com/ws/-/ws-7.2.3.tgz#a5411e1fb04d5ed0efee76d26d5c46d830c39b46" integrity sha512-HTDl9G9hbkNDk98naoR/cHDws7+EyYMOdL1BmjsZXRUjf7d+MficC4B7HLUPlSiho0vg+CWKrGIt/VJBd1xunQ== +ws@7.4.6, ws@~7.4.2: + version "7.4.6" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" + integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== + ws@7.5.3: version "7.5.3" resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.3.tgz#160835b63c7d97bfab418fc1b8a9fced2ac01a74" @@ -22160,11 +22634,6 @@ ws@^6.1.2: dependencies: async-limiter "~1.0.0" -ws@~7.4.2: - version "7.4.6" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" - integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== - xdg-basedir@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4" From b14d491d43f5415a035cd7f3f47dcff6d5614be6 Mon Sep 17 00:00:00 2001 From: gratestas Date: Mon, 9 Jan 2023 14:57:57 +0300 Subject: [PATCH 002/132] chore(eslintrc): temporarily remove the console log rule --- .eslintrc.json | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 8961ffb2..da1450f7 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -5,7 +5,8 @@ "plugin:react-hooks/recommended", "plugin:import/recommended", "plugin:import/react", - "plugin:security/recommended" + "plugin:security/recommended", + "prettier" ], "plugins": [ "react", @@ -28,7 +29,7 @@ "ecmaFeatures": { "jsx": true }, - "ecmaVersion": 2018, + "ecmaVersion": 2020, "sourceType": "module" }, "settings": { @@ -56,17 +57,6 @@ "argsIgnorePattern": "(^_+[0-9]*$)|([iI]gnored$)|(^ignored)" } ], - "no-console": [ - "error", - { - "allow": [ - "warn", - "error", - "info", - "debug" - ] - } - ], "security/detect-object-injection": "off", "security/detect-non-literal-fs-filename": "off", "import/extensions": [ From 70227fcc713ba8313dae49fadd1cd7d1ce917e96 Mon Sep 17 00:00:00 2001 From: gratestas Date: Mon, 9 Jan 2023 15:01:48 +0300 Subject: [PATCH 003/132] refactor: refactor and move constants to a single directory --- src/consts/appealSide.js | 30 +++++++++++++++++++ src/consts/defaultChainId.js | 9 ++++++ src/consts/disputeRuling.js | 15 ++++++++++ src/consts/disputeStatus.js | 6 ++++ src/consts/resolutionReason.js | 5 ++++ src/consts/supportedChains.js | 55 ++++++++++++++++++++++++++++++++++ src/consts/taskStatus.js | 7 +++++ src/consts/time.js | 4 +++ 8 files changed, 131 insertions(+) create mode 100644 src/consts/appealSide.js create mode 100644 src/consts/defaultChainId.js create mode 100644 src/consts/disputeRuling.js create mode 100644 src/consts/disputeStatus.js create mode 100644 src/consts/resolutionReason.js create mode 100644 src/consts/supportedChains.js create mode 100644 src/consts/taskStatus.js create mode 100644 src/consts/time.js diff --git a/src/consts/appealSide.js b/src/consts/appealSide.js new file mode 100644 index 00000000..f29d8c1c --- /dev/null +++ b/src/consts/appealSide.js @@ -0,0 +1,30 @@ +import { TaskParty } from '~/features/tasks'; +import disputeRuling from './disputeRuling'; + +const AppealSide = Object.freeze({ + Winner: 'winner', + Loser: 'loser', + Tie: 'tie', + None: undefined, +}); + +const rulingToAppealSideMap = { + [disputeRuling.TranslationApproved]: { + [TaskParty.Translator]: AppealSide.Winner, + [TaskParty.Challenger]: AppealSide.Loser, + }, + [disputeRuling.TranslationRejected]: { + [TaskParty.Translator]: AppealSide.Loser, + [TaskParty.Challenger]: AppealSide.Winner, + }, + [disputeRuling.RefuseToRule]: AppealSide.Tie, +}; + +export const mapRulingAndPartyToAppealSide = (ruling, party) => { + if (![TaskParty.Translator, TaskParty.Challenger].includes(party)) { + return AppealSide.None; + } + return rulingToAppealSideMap[ruling][party] || AppealSide.None; +}; + +export default AppealSide; diff --git a/src/consts/defaultChainId.js b/src/consts/defaultChainId.js new file mode 100644 index 00000000..54998cde --- /dev/null +++ b/src/consts/defaultChainId.js @@ -0,0 +1,9 @@ +// const env = process.env.PRODUCTION ?? 'development'; + +/* const defaultChainIdsPerEnv = { + true: Number(process.env.DEFAULT_CHAIN_ID) ?? 100, + development: Number(process.env.DEFAULT_CHAIN_ID) ?? 77, +}; */ + +export const defaultChainId = 100; +// export const defaultChainId = defaultChainIdsPerEnv[env] ?? 77; diff --git a/src/consts/disputeRuling.js b/src/consts/disputeRuling.js new file mode 100644 index 00000000..7e6db066 --- /dev/null +++ b/src/consts/disputeRuling.js @@ -0,0 +1,15 @@ +const disputeRuling = Object.freeze({ + None: undefined, + RefuseToRule: 'None', + TranslationApproved: 'Approve', + TranslationRejected: 'Reject', +}); + +export default disputeRuling; + +export const mapRulingToParty = { + [disputeRuling.None]: 0, + [disputeRuling.RefuseToRule]: 0, + [disputeRuling.TranslationApproved]: 1, + [disputeRuling.TranslationRejected]: 2, +}; diff --git a/src/consts/disputeStatus.js b/src/consts/disputeStatus.js new file mode 100644 index 00000000..31db9aa8 --- /dev/null +++ b/src/consts/disputeStatus.js @@ -0,0 +1,6 @@ +export default Object.freeze({ + None: undefined, + Waiting: 0, + Appeable: 1, + Solved: 2, +}); diff --git a/src/consts/resolutionReason.js b/src/consts/resolutionReason.js new file mode 100644 index 00000000..6061a978 --- /dev/null +++ b/src/consts/resolutionReason.js @@ -0,0 +1,5 @@ +export default Object.freeze({ + Accepted: 'translation-accepted', + Settled: 'dispute-settled', + Reimbursed: 'requester-reimbursed', +}); diff --git a/src/consts/supportedChains.js b/src/consts/supportedChains.js new file mode 100644 index 00000000..3ffa7906 --- /dev/null +++ b/src/consts/supportedChains.js @@ -0,0 +1,55 @@ +import * as dotenv from 'dotenv'; +dotenv.config(); + +export const RPC_URLS = JSON.parse(process.env.JSON_RPC_URLS); + +export const NETWORKS = Object.freeze({ + ethereum: 1, + gnosis: 100, + sokol: 77, +}); + +export const SUPPORTED_CHAINS = { + [NETWORKS.ethereum]: { + chainName: 'Ethereum Mainnet', + shortName: 'Mainnet', + sideChainId: false, + nativeCurrency: { name: 'ETH', symbol: 'ETH', decimals: 18 }, + rpcUrls: [RPC_URLS[NETWORKS.ethereum]], + blockExplorerUrls: ['https://etherscan.io'], + }, + [NETWORKS.gnosis]: { + chainName: 'Gnosis Chain', + shortName: 'Gnosis', + sideChain: true, + nativeCurrency: { name: 'xDAI', symbol: 'xDAI', decimals: 18 }, + rpcUrls: [RPC_URLS[NETWORKS.xDai]], + blockExplorerUrls: ['https://blockscout.com/xdai/mainnet'], + }, + [NETWORKS.sokol]: { + chainName: 'Poa Network Sokol', + shortName: 'Sokol', + sideChain: true, + nativeCurrency: { name: 'Sokol POA', symbol: 'SPOA', decimals: 18 }, + rpcUrls: [RPC_URLS[NETWORKS.sokol]], + blockExplorerUrls: ['https://blockscout.com/poa/sokol'], + }, +}; + +const counterPartyChainIdMap = { + [NETWORKS.ethereum]: NETWORKS.gnosis, + [NETWORKS.gnosis]: NETWORKS.ethereum, +}; + +export const SUPPORTED_CHAINIDS = Object.keys(SUPPORTED_CHAINS).map(x => parseInt(x)); + +export const isSupportedChain = chainId => SUPPORTED_CHAINIDS.includes(chainId); +export const isSupportedSideChain = chainId => SUPPORTED_CHAINS[chainId].sideChain; +export const getCounterPartyChainId = chainId => counterPartyChainIdMap[chainId]; + +export const getNetworkName = chainId => SUPPORTED_CHAINS[chainId].chainName ?? ''; +export const getNetworkShortName = chainId => SUPPORTED_CHAINS[chainId].shortName ?? ''; + +export const getBaseUrl = chainId => SUPPORTED_CHAINS[chainId].blockExplorerUrls[0]; +export const getAddressUrl = (chainId, address) => `${getBaseUrl(chainId)}/address/${address}`; +export const getTransactionUrl = (chainId, txHash) => `${getBaseUrl(chainId)}/tx/${txHash}`; diff --git a/src/consts/taskStatus.js b/src/consts/taskStatus.js new file mode 100644 index 00000000..da613f99 --- /dev/null +++ b/src/consts/taskStatus.js @@ -0,0 +1,7 @@ +export default Object.freeze({ + Created: 'Created', + Assigned: 'Assigned', + AwaitingReview: 'AwaitingReview', + DisputeCreated: 'DisputeCreated', + Resolved: 'Resolved', +}); diff --git a/src/consts/time.js b/src/consts/time.js new file mode 100644 index 00000000..3df08890 --- /dev/null +++ b/src/consts/time.js @@ -0,0 +1,4 @@ +export const _1_MINUTE_MS = 60 * 1000; +export const _1_SECOND_IN_MILISECONDS = 1000; +export const _5_MINUTES_IN_MILISECONDS = 5 * 60 * 1000; +export const _1_DAY_IN_SECONDS = 24 * 60 * 60; From a4566a085581509f9f05faffb22e33d81054211e Mon Sep 17 00:00:00 2001 From: gratestas Date: Mon, 9 Jan 2023 15:08:22 +0300 Subject: [PATCH 004/132] refactor: refactor web3 connectors --- src/connectors/Network.js | 15 +++++++++++++++ src/connectors/index.js | 25 +++++++++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 src/connectors/Network.js create mode 100644 src/connectors/index.js diff --git a/src/connectors/Network.js b/src/connectors/Network.js new file mode 100644 index 00000000..90fa9c4a --- /dev/null +++ b/src/connectors/Network.js @@ -0,0 +1,15 @@ +import { NetworkConnector as NetworkConnectorCore } from '@web3-react/network-connector'; + +export class NetworkConnector extends NetworkConnectorCore { + pause() { + if (this.active) { + this.providers[this.currentChainId].stop(); + } + } + + resume() { + if (this.active) { + this.providers[this.currentChainId].start(); + } + } +} diff --git a/src/connectors/index.js b/src/connectors/index.js new file mode 100644 index 00000000..d21acd4a --- /dev/null +++ b/src/connectors/index.js @@ -0,0 +1,25 @@ +import { InjectedConnector } from '@web3-react/injected-connector'; +import { WalletConnectConnector } from '@web3-react/walletconnect-connector'; + +import { SUPPORTED_CHAINIDS, RPC_URLS } from '../consts/supportedChains'; +import { defaultChainId } from '../consts/defaultChainId'; +import { NetworkConnector } from './Network'; + +export const injected = new InjectedConnector({ + supportedChainIds: SUPPORTED_CHAINIDS, +}); +injected.name = 'injected'; + +export const walletConnect = new WalletConnectConnector({ + rpc: RPC_URLS, +}); +walletConnect.name = 'walletConnect'; + +export const network = new NetworkConnector({ + urls: RPC_URLS, + pollingInterval: 20000, + defaultChainId, +}); +network.name = 'network'; + +export const connectors = { injected, walletConnect, network }; From 580c082d385fbe4beb7db9d72b4d47c69d593161 Mon Sep 17 00:00:00 2001 From: gratestas Date: Mon, 9 Jan 2023 15:10:51 +0300 Subject: [PATCH 005/132] feat: create hooks to query data from subgraph --- src/hooks/queries/useEvidencesByTaskQuery.js | 38 +++++++++++++ src/hooks/queries/useIPFSQuery.js | 19 +++++++ src/hooks/queries/useRoundQuery.js | 45 +++++++++++++++ src/hooks/queries/useTaskQuery.js | 55 +++++++++++++++++++ src/hooks/queries/useTasksByRequesterQuery.js | 47 ++++++++++++++++ src/hooks/queries/useTasksQuery.js | 47 ++++++++++++++++ 6 files changed, 251 insertions(+) create mode 100644 src/hooks/queries/useEvidencesByTaskQuery.js create mode 100644 src/hooks/queries/useIPFSQuery.js create mode 100644 src/hooks/queries/useRoundQuery.js create mode 100644 src/hooks/queries/useTaskQuery.js create mode 100644 src/hooks/queries/useTasksByRequesterQuery.js create mode 100644 src/hooks/queries/useTasksQuery.js diff --git a/src/hooks/queries/useEvidencesByTaskQuery.js b/src/hooks/queries/useEvidencesByTaskQuery.js new file mode 100644 index 00000000..25b8d756 --- /dev/null +++ b/src/hooks/queries/useEvidencesByTaskQuery.js @@ -0,0 +1,38 @@ +import useSWR from 'swr'; +import { gql } from 'graphql-request'; + +const evidencesByTaskQuery = gql` + query Evidences($id: ID!) { + evidences(where: { task_: { id: $id } }, orderBy: timestamp, orderDirection: desc) { + id + arbitrator + URI + number + party + task { + id + } + timestamp + } + } +`; + +export const useEvidencesByTaskQuery = id => { + const { data, error, isValidating } = useSWR(() => + typeof id !== 'undefined' + ? { + query: evidencesByTaskQuery, + variables: { id: id }, + } + : false + ); + + if (isValidating) { + console.log('Evidence data is being fetched'); + } else if (error) { + console.log('Error loading evidence data'); + } else if (data) { + console.log('Evidence data is already cached'); + } + return { evidences: data ? data.evidences : null, isLoading: !error && !data, error: error }; +}; diff --git a/src/hooks/queries/useIPFSQuery.js b/src/hooks/queries/useIPFSQuery.js new file mode 100644 index 00000000..ac864037 --- /dev/null +++ b/src/hooks/queries/useIPFSQuery.js @@ -0,0 +1,19 @@ +import useSWRImmutable from 'swr/immutable'; + +export const useIPFSQuery = ipfsPath => { + const { data, error } = useSWRImmutable( + () => (ipfsPath !== undefined ? ipfsPath : false), + async () => { + console.log('ipfsQuery'); + if (ipfsPath) { + return fetch(`https://ipfs.kleros.io${ipfsPath}`).then(res => res.json()); + } else throw Error; + } + ); + const result = data || undefined; + return { + data: result, + isLoading: !error && !data, + error: error, + }; +}; diff --git a/src/hooks/queries/useRoundQuery.js b/src/hooks/queries/useRoundQuery.js new file mode 100644 index 00000000..5e4332fe --- /dev/null +++ b/src/hooks/queries/useRoundQuery.js @@ -0,0 +1,45 @@ +import useSWR from 'swr'; +import { gql } from 'graphql-request'; + +const roundQuery = gql` + query Round($id: ID!) { + round(id: $id) { + id + amountPaidTranslator + amountPaidChallenger + appealed + appealedAt + appealPeriodStart + appealPeriodEnd + creationTime + feeRewards + hasPaidChallenger + hasPaidTranslator + numberOfContributions + ruling + rulingTime + task { + id + taskID + disputeID + arbitrator + } + } + } +`; + +export const useRoundQuery = id => { + const { data, error, isValidating } = useSWR({ + query: roundQuery, + variables: { id: id }, + }); + + if (isValidating) { + console.log('Round data is being fetched'); + } else if (error) { + console.log('Round loading task data'); + } else if (data) { + console.log('Round data is already cached'); + } + return { round: data ? data.round : null, isLoading: !error && !data, error }; +}; diff --git a/src/hooks/queries/useTaskQuery.js b/src/hooks/queries/useTaskQuery.js new file mode 100644 index 00000000..6f4fa29e --- /dev/null +++ b/src/hooks/queries/useTaskQuery.js @@ -0,0 +1,55 @@ +import useSWR from 'swr'; +import { gql } from 'graphql-request'; + +const taskQuery = gql` + query Task($id: ID!) { + task(id: $id) { + id + taskID + challenger + deadline + disputed + disputeID + finalRuling + lang + lastInteraction + submissionTimeout + minPrice + maxPrice + metaEvidence { + id + metaEvidenceID + URI + } + numberOfEvidences + numberOfRounds + reason + requester + requesterDeposit + status + sumDeposit + translation + translator + } + } +`; + +export const useTaskQuery = id => { + const { data, error, isValidating } = useSWR(() => + typeof id !== 'undefined' + ? { + query: taskQuery, + variables: { id: id }, + } + : false + ); + + if (isValidating) { + console.log('Task data is being fetched'); + } else if (error) { + console.log('Error loading task data'); + } else if (data) { + console.log('Task data is already cached'); + } + return { task: data ? data.task : null, isLoading: !error && !data, isError: error }; +}; diff --git a/src/hooks/queries/useTasksByRequesterQuery.js b/src/hooks/queries/useTasksByRequesterQuery.js new file mode 100644 index 00000000..8bc5c4aa --- /dev/null +++ b/src/hooks/queries/useTasksByRequesterQuery.js @@ -0,0 +1,47 @@ +import useSWR from 'swr'; +import { gql } from 'graphql-request'; + +const tasksByRequesterQuery = gql` + query TasksByRequester($skip: Int, $requester: String) { + tasks(first: 30, skip: $skip, where: { requester: $requester }, orderBy: taskID, orderDirection: desc) { + id + taskID + challenger + deadline + disputed + disputeID + finalRuling + lang + lastInteraction + submissionTimeout + minPrice + maxPrice + metaEvidence { + id + metaEvidenceID + URI + } + requester + requesterDeposit + status + translation + translator + } + } +`; + +export const useTasksByRequesterQuery = (requester, skip) => { + const { data, error, isValidating } = useSWR({ + query: tasksByRequesterQuery, + variables: { skip: skip, requester: requester }, + }); + + if (isValidating) { + console.log('RequesterTasks data is being fetched'); + } else if (error) { + console.log('Error loading RequesterTasks data'); + } else if (data) { + console.log('RequesterTasks data is already cached'); + } + return { tasks: data ? data.tasks : null, isLoading: !error && !data, error }; +}; diff --git a/src/hooks/queries/useTasksQuery.js b/src/hooks/queries/useTasksQuery.js new file mode 100644 index 00000000..6b4bf5d2 --- /dev/null +++ b/src/hooks/queries/useTasksQuery.js @@ -0,0 +1,47 @@ +import useSWR from 'swr'; +import { gql } from 'graphql-request'; + +const tasksQuery = gql` + query TasksPage($skip: Int) { + tasks(first: 30, skip: $skip, orderBy: taskID, orderDirection: desc) { + id + taskID + challenger + deadline + disputed + disputeID + finalRuling + lang + lastInteraction + submissionTimeout + minPrice + maxPrice + metaEvidence { + id + metaEvidenceID + URI + } + requester + requesterDeposit + status + translation + translator + } + } +`; + +export const useTasksQuery = skip => { + const { data, error, isValidating } = useSWR({ + query: tasksQuery, + variables: { skip: skip }, + }); + + if (isValidating) { + console.log('Task data is being fetched'); + } else if (error) { + console.log('Error loading task data'); + } else if (data) { + console.log('Task data is already cached'); + } + return { tasks: data ? data.tasks : null, isLoading: !error && !data, isError: error }; +}; From 1c723e925a34da6598ee7efa93cdf6c67781614e Mon Sep 17 00:00:00 2001 From: gratestas Date: Mon, 9 Jan 2023 15:12:47 +0300 Subject: [PATCH 006/132] feat: create custom hooks --- src/hooks/useBalance.js | 26 +++ src/hooks/useConnect.js | 48 +++++ src/hooks/useCurrentParty.js | 32 ++++ src/hooks/useDispute.js | 113 ++++++++++++ src/hooks/useLinguo.js | 291 +++++++++++++++++++++++++++++++ src/hooks/useParamsCustom.js | 16 ++ src/hooks/useRemainingTime.js | 44 +++++ src/hooks/useTask.js | 67 +++++++ src/hooks/useTasksFilter.js | 0 src/hooks/useTransactionState.js | 47 +++++ src/hooks/useWeb3.js | 80 +++++++++ 11 files changed, 764 insertions(+) create mode 100644 src/hooks/useBalance.js create mode 100644 src/hooks/useConnect.js create mode 100644 src/hooks/useCurrentParty.js create mode 100644 src/hooks/useDispute.js create mode 100644 src/hooks/useLinguo.js create mode 100644 src/hooks/useParamsCustom.js create mode 100644 src/hooks/useRemainingTime.js create mode 100644 src/hooks/useTask.js create mode 100644 src/hooks/useTasksFilter.js create mode 100644 src/hooks/useTransactionState.js create mode 100644 src/hooks/useWeb3.js diff --git a/src/hooks/useBalance.js b/src/hooks/useBalance.js new file mode 100644 index 00000000..3c90099e --- /dev/null +++ b/src/hooks/useBalance.js @@ -0,0 +1,26 @@ +import { useEffect, useState } from 'react'; +import { useWeb3 } from './useWeb3'; + +export const useBalance = () => { + const { account, library: provider } = useWeb3(); + const [balance, setBalance] = useState(); + const [status, setStatus] = useState(); + + useEffect(() => { + const getBalance = async () => { + setStatus('pending'); + try { + if (account) { + const value = await provider.getBalance(account); + setBalance(Number(value)); + setStatus('succeeded'); + } + } catch (error) { + console.warn('Failed to get the account balance:', error); + setStatus('failed'); + } + }; + getBalance(); + }, [account, provider, setStatus]); + return { balance, status }; +}; diff --git a/src/hooks/useConnect.js b/src/hooks/useConnect.js new file mode 100644 index 00000000..7b13f849 --- /dev/null +++ b/src/hooks/useConnect.js @@ -0,0 +1,48 @@ +import { useEffect, useState } from 'react'; +import { useWeb3React, UnsupportedChainIdError } from '@web3-react/core'; +import { connectors } from '~/connectors'; +import { WalletConnectConnector } from '@web3-react/walletconnect-connector'; +import { useInactiveListener } from './useWeb3'; + +export const useConnect = () => { + const { activate, active, deactivate, error, connector } = useWeb3React(); + const [connecting, setConnecting] = useState(false); + const [activationError, setActivationError] = useState(); + useInactiveListener(); + + const connect = async connectorName => { + const connector = connectors[connectorName]; + /** + * WalletConnect provider doesn't work after user rejects the request the first time: + * @see { @link https://github.com/NoahZinsmeister/web3-react/issues/217 } + */ + if (connector instanceof WalletConnectConnector && connector.walletConnectProvider?.wc?.uri) { + connector.walletConnectProvider = undefined; + } + + try { + setConnecting(true); + await activate(connector, undefined, true); + } catch (err) { + setActivationError(err); + } + setConnecting(false); + }; + + const disconnect = () => { + if (connector instanceof WalletConnectConnector) { + /** + * Cleans up wallet connect, otherwise it won't show the QR code when connecting again. + */ + window.localStorage.removeItem('walletconnect'); + } + + deactivate(); + }; + + useEffect(() => { + if (activationError && activationError.name === UnsupportedChainIdError.name) throw activationError; + }, [activationError]); + + return { connect, disconnect, active, connecting, error }; +}; diff --git a/src/hooks/useCurrentParty.js b/src/hooks/useCurrentParty.js new file mode 100644 index 00000000..fc83a772 --- /dev/null +++ b/src/hooks/useCurrentParty.js @@ -0,0 +1,32 @@ +import { TaskParty } from '~/features/tasks'; +import { useParamsCustom } from '~/hooks/useParamsCustom'; +import { useTask } from '~/hooks/useTask'; +import { useWeb3 } from '~/hooks/useWeb3'; + +const getCurrentParty = ({ account, requester, translator, challenger }) => { + switch (account) { + /** + * The requester could also be the challenger. + * If that happens, the role he should assume is the one + * of challenger, so he can better informed of next steps. + * That's why challenger is matched before requester here. + */ + case challenger: + return TaskParty.Challenger; + case requester: + return TaskParty.Requester; + case translator: + return TaskParty.Translator; + default: + return TaskParty.Other; + } +}; + +export default function useCurrentParty() { + const { account, chainId } = useWeb3(); + const { id } = useParamsCustom(chainId); + const { task } = useTask(id); + + const { challenger, requester, translator } = task; + return getCurrentParty({ account, requester, translator, challenger }); +} diff --git a/src/hooks/useDispute.js b/src/hooks/useDispute.js new file mode 100644 index 00000000..d99d2751 --- /dev/null +++ b/src/hooks/useDispute.js @@ -0,0 +1,113 @@ +import { useCallback, useMemo } from 'react'; +import { useRoundQuery } from './queries/useRoundQuery'; +import { useLinguo } from './useLinguo'; + +import Dispute from '~/utils/dispute'; +import { TaskParty } from '~/features/tasks'; + +export const useDispute = (id, roundId) => { + const { round, isLoading, error } = useRoundQuery(roundId); + const linguo = useLinguo(); + + const status = linguo.getDisputeStatus(id); + const appealCost = linguo.getAppealCost(id); + const rewardPoolParams = linguo.getRewardPoolParams(); + + const isWaiting = useMemo(() => { + if (status !== undefined) return Dispute.isWaiting(status); + }, [status]); + + const isSolved = useMemo(() => { + if (status !== undefined) return Dispute.isSolved(status); + }, [status]); + + const isAppealable = useMemo(() => { + if (status !== undefined) return Dispute.isAppealable(status); + }, [status]); + + const remainingTimeForTranslator = useMemo(() => { + if (status !== undefined) + return Dispute.getRemainingTimeForAppeal( + status, + round.ruling, + round.appealPeriodStart, + round.appealPeriodEnd, + TaskParty.Translator + ); + }, [round?.appealPeriodEnd, round?.appealPeriodStart, round?.ruling, status]); + + const remainingTimeForChallenger = useMemo(() => { + if (status !== undefined) { + return Dispute.getRemainingTimeForAppeal( + status, + round.ruling, + round.appealPeriodStart, + round.appealPeriodEnd, + TaskParty.Challenger + ); + } + }, [round?.appealPeriodEnd, round?.appealPeriodStart, round?.ruling, status]); + + const isAppealOngoing = useMemo(() => { + if (status !== undefined) + return Dispute.isAppealOngoing(status, round.ruling, { + remainingTime: { + [TaskParty.Translator]: remainingTimeForTranslator, + [TaskParty.Challenger]: remainingTimeForChallenger, + }, + hasPaidFees: { + [TaskParty.Translator]: round.hasPaidTranslator, + [TaskParty.Challenger]: round.hasPaidChallenger, + }, + }); + }, [ + status, + round?.ruling, + round?.hasPaidTranslator, + round?.hasPaidChallenger, + remainingTimeForTranslator, + remainingTimeForChallenger, + ]); + + const rewardPool = useMemo(() => { + if (status !== undefined && appealCost !== undefined) { + return Dispute.getRewardPool(status, round.ruling, appealCost, rewardPoolParams); + } + }, [appealCost, rewardPoolParams, round?.ruling, status]); + + const totalAppealCost = useCallback( + party => appealCost && Dispute.getTotalAppealCost(appealCost, rewardPool, party), + [appealCost, rewardPool] + ); + const fundingROI = party => Dispute.getFundingROI(appealCost, rewardPool, party); + + const expectedFinalRuling = useMemo(() => { + if (status !== undefined) { + return Dispute.getExpectedFinalRuling(status, round.ruling, isAppealOngoing, { + hasPaidFees: { + [TaskParty.Translator]: round.hasPaidTranlator, + [TaskParty.Challenger]: round.hasPaidChallenger, + }, + }); + } + }, [isAppealOngoing, round?.hasPaidChallenger, round?.hasPaidTranlator, round?.ruling, status]); + + return { + dispute: { + ...round, + appealCost, + expectedFinalRuling, + fundingROI, + isAppealable, + isAppealOngoing, + isSolved, + isWaiting, + remainingTimeForChallenger, + remainingTimeForTranslator, + status, + totalAppealCost, + }, + isLoading: isLoading || status === undefined, + error, + }; +}; diff --git a/src/hooks/useLinguo.js b/src/hooks/useLinguo.js new file mode 100644 index 00000000..7a65d01e --- /dev/null +++ b/src/hooks/useLinguo.js @@ -0,0 +1,291 @@ +/* eslint-disable react-hooks/rules-of-hooks */ +import { Contract } from '@ethersproject/contracts'; +import useSWRImmutable from 'swr/immutable'; +import useSWR from 'swr'; + +import Linguo from '@kleros/linguo-contracts/artifacts/contracts/0.7.x/Linguo.sol/Linguo.json'; +import IArbitrator from '@kleros/erc-792/build/contracts/IArbitrator.json'; + +import { useWeb3 } from '~/hooks/useWeb3'; +import { useParamsCustom } from '~/hooks/useParamsCustom'; +import { BigNumber } from 'ethers'; +import { useEffect, useState } from 'react'; + +const fetcher = (library, abi) => args => { + if (!library) return; + const [arg1, arg2, ...params] = args; + const address = arg1; + const method = arg2; + const contract = new Contract(address, abi, library); + return contract[method](...params); +}; + +export const getReviewTimeout = async (address, library) => { + const contract = new Contract(address, Linguo.abi, library); + return await contract.reviewTimeout(); +}; + +/* const logger = useSWRNext => { + return (key, fetcher, config) => { + // Add logger to the original fetcher. + const extendedFetcher = (...args) => { + console.log('SWR Request:', key); + return fetcher(...args); + }; + + // Execute the hook with the new fetcher. + return useSWRNext(key, extendedFetcher, config); + }; +}; */ + +export const useLinguo = () => { + const { account, chainId, library } = useWeb3(); + const { address } = useParamsCustom(chainId); + + const call = (method, ...params) => { + const { data } = useSWR([address, method, ...params], { + fetcher: fetcher(library, Linguo.abi), + }); + + return typeof data === 'undefined' ? 0 : data instanceof BigNumber ? data : String(data); + }; + + const _send = async (method, ...params) => { + try { + const contract = new Contract(address, Linguo.abi, library); + const tx = await contract.connect(account).methods[method](...params); + // invalidate the cache so that the data is refetched + // mutate([address, method, ...params]) + return tx; + } catch (error) { + console.error(error); + } + }; + + return { + call: (method, ...params) => { + const { data } = useSWR([address, method, ...params], { + fetcher: fetcher(library, Linguo.abi), + }); + + return typeof data === 'undefined' ? 0 : data instanceof BigNumber ? data : String(data); + }, + send: async (method, ...params) => { + try { + const contract = new Contract(address, Linguo.abi, library); + const tx = await contract.connect(account).methods[method](...params); + // invalidate the cache so that the data is refetched + // mutate([address, method, ...params]) + return tx; + } catch (error) { + console.error(error); + } + }, + getArbitrationCost: () => { + const { data: arbitratorExtraData } = useSWRImmutable([address, 'arbitratorExtraData'], { + fetcher: fetcher(library, Linguo.abi), + }); + + const { data: arbitrator } = useSWR([address, 'arbitrator'], { + fetcher: fetcher(library, Linguo.abi), + }); + + const { data: arbitrationCost } = useSWR([arbitrator, 'arbitrationCost', arbitratorExtraData], { + fetcher: fetcher(library, IArbitrator.abi), + }); + return arbitrationCost; + }, + getDisputeStatus: disputeID => { + const { data: arbitrator } = useSWR([address, 'arbitrator'], { + fetcher: fetcher(library, Linguo.abi), + }); + const { data: status } = useSWR( + typeof arbitrator !== 'undefined' ? [arbitrator, 'disputeStatus', disputeID] : false, + { + fetcher: fetcher(library, IArbitrator.abi), + } + ); + return status; + }, + getAppealCost: disputeID => { + const { data: arbitratorExtraData } = useSWRImmutable([address, 'arbitratorExtraData'], { + fetcher: fetcher(library, Linguo.abi), + }); + // TODO: check if it is reasonable to implement SWR middleware + const { data: arbitrator } = useSWR([address, 'arbitrator'], { + fetcher: fetcher(library, Linguo.abi), + }); + + const { data: appealCost } = useSWR([arbitrator, 'appealCost', disputeID, arbitratorExtraData], { + fetcher: fetcher(library, IArbitrator.abi), + }); + return appealCost; + }, + getChallengeDeposit: taskID => { + return call('getChallengeValue', taskID); + }, + getTranslatorDeposit: taskID => { + return call('getDepositValue', taskID); + }, + getRewardPoolParams: () => { + const winnerStakeMultiplier = call('winnerStakeMultiplier'); + const loserStakeMultiplier = call('loserStakeMultiplier'); + const sharedStakeMultiplier = call('sharedStakeMultiplier'); + const multiplierDivisor = call('MULTIPLIER_DIVISOR'); + + return { winnerStakeMultiplier, loserStakeMultiplier, sharedStakeMultiplier, multiplierDivisor }; + }, + createTask: async args => { + return await _send('createTask', args); + }, + }; +}; + +/* const getArbitrationCost = () => { + // Create the SWR middleware function + const swrMiddleware = (useSWRNext) => { + return (key, fetcher, config) => { + // Use a ref to store the data from each hook + const dataRef = useRef({}); + + // Actual SWR hook + const swr = useSWRNext(key, fetcher, config); + + useEffect(() => { + // Update the dataRef with the current hook's data + if (swr.data !== undefined) { + dataRef.current[key] = swr.data; + } + }, [swr.data]); + + // Fetch the next hook when the data for the previous hook is available + useEffect(() => { + if (dataRef.current[key]) { + // Set the next hook's key and dependencies based on the current hook's data + const nextKey = [dataRef.current[key], 'arbitrator', dataRef.current[[address, 'arbitratorExtraData']]]; + const nextDependencies = [dataRef.current[key], dataRef.current[[address, 'arbitratorExtraData']]]; + swr.mutate(nextKey, fetcher, config, nextDependencies); + } + }, [dataRef.current[key]]); + + return swr; + } + } + + // Use the custom SWR hook with the middleware + const { data: arbitrationCost } = useSWR([address, 'arbitratorExtraData'], { + fetcher: fetcher(library, Linguo.abi), + middleware: swrMiddleware, + }); + + return arbitrationCost; + } + */ + +/* +const useSWRWithMiddleware = (key, fetcher, middleware) => { + return useSWR(key, { + fetcher, + middleware, + }); +}; + +const getArbitrationCost = () => { + const { data: arbitratorExtraData } = useSWR([address, 'arbitratorExtraData'], { + fetcher: fetcher(library, Linguo.abi), + }); + + const { data: arbitrator } = useSWR([address, 'arbitrator'], { + fetcher: fetcher(library, Linguo.abi), + }); + + // Use the helper function to wrap the useSWR hook in the middleware. + const { data: arbitrationCost } = useSWRWithMiddleware( + [arbitrator, 'arbitrationCost'], + fetcher(library, IArbitrator.abi), + [arbitratorExtraData, arbitrator] + ); + + return arbitrationCost; +}; */ + +export const useLinguoApi = () => { + const { account, chainId, library } = useWeb3(); + const { address: contractAddress } = useParamsCustom(chainId); + const [address, setAddress] = useState(); + + // const contractAdressesByLang = JSON.parse(process.env.LINGUO_CONTRACT_ADDRESSES); + + useEffect(() => { + if (!contractAddress) return; + setAddress(contractAddress); + }, [contractAddress]); + + const _call = async (address, deployment, method, ...args) => { + try { + const contract = new Contract(address, deployment, library); + + const numberOfArgs = contract.interface.getFunction(method).inputs.length; + if (args.length !== numberOfArgs && args.length !== numberOfArgs + 1) { + throw new Error(`Invalid number of arguments for function: ${method} `); + } + + return await contract[method](...args); + } catch (error) { + console.error(error); + } + }; + + const _send = async (method, { args, value }) => { + const contract = new Contract(address, Linguo.abi, library.getSigner()); + + const numberOfArgs = contract.interface.getFunction(method).inputs.length; + if (args.length !== numberOfArgs && args.length !== numberOfArgs + 1) { + throw new Error(`Invalid number of arguments for function: ${method} `); + } + + const tx = await contract[method](...args, { value: value, gasLimit: 10000000 }); + return tx; + }; + + return { + address, + setAddress, + createTask: async (deadline, minPrice, metaEvidence, maxPrice) => { + return await _send(LinguoInteraction.createTask, { + args: [deadline, minPrice, metaEvidence], + value: maxPrice, + }); + }, + assignTask: async taskID => { + const translationDeposit = await _call(address, Linguo.abi, 'getDepositValue', taskID); + return await _send(LinguoInteraction.assignTask, { args: [taskID], value: translationDeposit }); + }, + + acceptTranlation: async taskID => { + return await _send(LinguoInteraction.acceptTranlation, { args: [taskID] }); + }, + challengeTranslation: async (taskID, evidence) => { + return await _send(LinguoInteraction.challengeTranslation, { args: [taskID, evidence] }); + }, + submitTranslation: async (taskID, translation) => { + return await _send('submitTranslation', { args: [taskID, translation] }); + }, + fundAppeal: async (taskID, party, totalAppealCost) => { + return await _send('fundAppeal', { args: [taskID, party], value: totalAppealCost }); + }, + reimburseRequester: async taskID => { + return await _send('reimburseRequester', { args: [taskID] }); + }, + withdrawFeesAndRewards: async taskID => { + return await _send('batchRoundWithdraw', { args: [account, taskID, 0, 0] }); + }, + }; +}; + +const LinguoInteraction = { + createTask: 'createTask', + assignTask: 'assignTask', + acceptTranslation: 'acceptTranlation', + challengeTranslation: 'challengeTranslation', +}; diff --git a/src/hooks/useParamsCustom.js b/src/hooks/useParamsCustom.js new file mode 100644 index 00000000..bcca8cf3 --- /dev/null +++ b/src/hooks/useParamsCustom.js @@ -0,0 +1,16 @@ +import { useParams } from 'react-router-dom'; + +export const useParamsCustom = chainId => { + const { id } = useParams(); + if (!id) return { address: '' }; + const [address, taskId] = id.split('/'); + + const contractAdressesByLang = JSON.parse(process.env.LINGUO_CONTRACT_ADDRESSES); + let langPair = ''; + if (!chainId) return { id: 0 }; + for (const [key, value] of Object.entries(contractAdressesByLang[chainId])) { + if (value.includes(address)) langPair = key; + } + + return { id: `${langPair}-${taskId}`, address }; +}; diff --git a/src/hooks/useRemainingTime.js b/src/hooks/useRemainingTime.js new file mode 100644 index 00000000..7e7e88af --- /dev/null +++ b/src/hooks/useRemainingTime.js @@ -0,0 +1,44 @@ +import moment from 'moment'; +import t from 'prop-types'; +import useCountdownTimer from '~/shared/useCountdownTimer'; +import { _1_DAY_IN_SECONDS, _1_SECOND_IN_MILISECONDS, _5_MINUTES_IN_MILISECONDS } from '~/consts/time'; + +export function useRemainingTime(initialValueSeconds) { + const remainingTime = useCountdownTimer({ + seconds: initialValueSeconds, + refreshInterval: remainingTimeInSeconds => + remainingTimeInSeconds < _1_DAY_IN_SECONDS ? _1_SECOND_IN_MILISECONDS : _5_MINUTES_IN_MILISECONDS, + }); + + return remainingTime; +} + +function RemainingTime({ initialValueSeconds, render, showPrefix }) { + const remainingTime = useRemainingTime(initialValueSeconds); + + const endingSoon = remainingTime < _1_DAY_IN_SECONDS; + const formattedValue = + remainingTime === 0 + ? '00:00:00' + : remainingTime < _1_DAY_IN_SECONDS + ? moment().startOf('day').add(remainingTime, 'second').format('HH:mm:ss') + : moment().add(remainingTime, 'second').fromNow(!showPrefix); + + return render({ value: remainingTime, formattedValue, endingSoon }); +} + +const defaultRender = ({ formattedValue }) => formattedValue; + +RemainingTime.propTypes = { + initialValueSeconds: t.number.isRequired, + render: t.func, + showPrefix: t.bool, +}; + +RemainingTime.defaultProps = { + initialValueSeconds: 0, + render: defaultRender, + showPrefix: false, +}; + +export default RemainingTime; diff --git a/src/hooks/useTask.js b/src/hooks/useTask.js new file mode 100644 index 00000000..7cb6c7fd --- /dev/null +++ b/src/hooks/useTask.js @@ -0,0 +1,67 @@ +import React from 'react'; + +import { useTaskQuery } from './queries/useTaskQuery'; +import { useIPFSQuery } from './queries/useIPFSQuery'; + +import useInterval from '~/shared/useInterval'; + +import { useWeb3 } from './useWeb3'; + +import Task from '~/utils/task'; +import taskStatus from '~/consts/taskStatus'; +import { _1_MINUTE_MS } from '~/consts/time'; + +export const useTask = id => { + const { account } = useWeb3(); + const { task, isLoading, isError } = useTaskQuery(id); + const { data } = useIPFSQuery(task?.metaEvidence?.URI); + + const { + challenger, + lastInteraction, + numberOfRounds, + requester, + requesterDeposit, + translation, + translator, + status, + submissionTimeout, + } = task; + + const { minPrice, maxPrice, wordCount } = data?.metadata; + + const latestRoundId = `${id}-${Number(numberOfRounds) - 1}`; + const [currentPrice, setCurrentPrice] = React.useState( + Task.getCurrentPrice(minPrice, maxPrice, lastInteraction, submissionTimeout, status) + ); + + const updateCurrentPrice = React.useCallback(() => { + const value = Task.getCurrentPrice(minPrice, maxPrice, lastInteraction, submissionTimeout, status); + setCurrentPrice(value); + }, [lastInteraction, maxPrice, minPrice, status, submissionTimeout]); + + const interval = requesterDeposit === undefined ? _1_MINUTE_MS : null; + useInterval(updateCurrentPrice, interval); + + const actualPrice = status === taskStatus.Assigned ? requesterDeposit : currentPrice; + const pricePerWord = Task.getCurrentPricePerWord(actualPrice, wordCount); + const isIncomoplete = Task.isIncomplete(status, translation, lastInteraction, submissionTimeout); + + const currentParty = Task.getCurrentParty(account, requester, translator, challenger); + + return { + task: { + ...task, + ...data?.metadata, + actualPrice, + currentPrice, + pricePerWord, + currentParty, + latestRoundId, + isLoading, + isIncomoplete, + }, + isLoading, + isError, + }; +}; diff --git a/src/hooks/useTasksFilter.js b/src/hooks/useTasksFilter.js new file mode 100644 index 00000000..e69de29b diff --git a/src/hooks/useTransactionState.js b/src/hooks/useTransactionState.js new file mode 100644 index 00000000..fe187ad1 --- /dev/null +++ b/src/hooks/useTransactionState.js @@ -0,0 +1,47 @@ +import { useState } from 'react'; + +const TRANSACTION_REJECTED_CODE = 4001; +const TRANSACTION_REJECTED = 'Transaction rejected by user'; +const SUCCESS = 'success'; +// const REJECTED = 'Rejected'; +const PENDING = 'pending'; +const ERROR = 'error'; +const DEFAULT = 'idle'; + +export const useTransactionState = contractCallbackFn => { + const [state, setState] = useState(DEFAULT); + const [message, setMessage] = useState(''); + + const handleTransaction = async () => { + if (!contractCallbackFn) return; + + setState(PENDING); + + try { + const tx = await contractCallbackFn(); + await tx.wait(); + setState(SUCCESS); + } catch (error) { + if (error.code === TRANSACTION_REJECTED_CODE) { + setMessage(TRANSACTION_REJECTED); + // setState(REJECTED); + setState(DEFAULT); + } else { + console.error(error); + setState(ERROR); + } + } + }; + + /* useEffect(() => { + if (state === 'rejected') toast.error(message); + reset(); + }, [message, state]); + */ + + return { + state, + message, + handleTransaction, + }; +}; diff --git a/src/hooks/useWeb3.js b/src/hooks/useWeb3.js new file mode 100644 index 00000000..db057b5c --- /dev/null +++ b/src/hooks/useWeb3.js @@ -0,0 +1,80 @@ +import { useState, useEffect } from 'react'; +import { useWeb3React } from '@web3-react/core'; +import { injected } from '../connectors'; + +export const useWeb3 = () => { + const context = useWeb3React(); + const contextNetwork = useWeb3React('NETWORK'); + + return context.active ? context : contextNetwork; +}; + +export function useEagerConnect() { + const { activate, active } = useWeb3React(); + + const [tried, setTried] = useState(false); + + useEffect(() => { + injected.isAuthorized().then(isAuthorized => { + if (isAuthorized) { + activate(injected, undefined, true).catch(() => { + setTried(true); + }); + } else { + setTried(true); + } + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); // intentionally only running on mount (make sure it's only mounted once :)) + + // if the connection worked, wait until we get confirmation of that to flip the flag + useEffect(() => { + if (!tried && active) { + setTried(true); + } + }, [active, tried]); + + return tried; +} + +export function useInactiveListener(suppress = false) { + const { active, error, activate } = useWeb3React(); + + useEffect(() => { + const { ethereum } = window; + if (ethereum && ethereum.on && !active && !error && !suppress) { + const handleConnect = () => { + console.log("Handling 'connect' event"); + activate(injected); + }; + const handleChainChanged = chainId => { + console.log("Handling 'chainChanged' event with payload", chainId); + activate(injected); + }; + const handleAccountsChanged = accounts => { + console.log("Handling 'accountsChanged' event with payload", accounts); + if (accounts.length > 0) { + activate(injected); + } + }; + const handleNetworkChanged = networkId => { + console.log("Handling 'networkChanged' event with payload", networkId); + activate(injected); + }; + + ethereum.on('connect', handleConnect); + ethereum.on('chainChanged', handleChainChanged); + ethereum.on('accountsChanged', handleAccountsChanged); + ethereum.on('networkChanged', handleNetworkChanged); + + return () => { + if (ethereum.removeListener) { + ethereum.removeListener('connect', handleConnect); + ethereum.removeListener('chainChanged', handleChainChanged); + ethereum.removeListener('accountsChanged', handleAccountsChanged); + ethereum.removeListener('networkChanged', handleNetworkChanged); + } + }; + } + }, [active, error, suppress, activate]); +} From af8b8bfea5c2af769deb617bb7ca5562037b678f Mon Sep 17 00:00:00 2001 From: gratestas Date: Mon, 9 Jan 2023 15:15:12 +0300 Subject: [PATCH 007/132] feat: create task helpers --- src/utils/task/index.js | 105 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 src/utils/task/index.js diff --git a/src/utils/task/index.js b/src/utils/task/index.js new file mode 100644 index 00000000..cebd7b28 --- /dev/null +++ b/src/utils/task/index.js @@ -0,0 +1,105 @@ +import taskStatus from '~/consts/taskStatus'; +import moment from 'moment'; +import { BigNumber } from 'ethers'; +import { TaskParty } from '~/features/tasks'; + +const isIncomplete = (status, translation, lastInteraction, submissionTimeout) => { + if (status === taskStatus.Resolved) { + return translation === ''; + } + + if ([taskStatus.Created, taskStatus.Assigned].includes(status)) { + return moment(lastInteraction).add(submissionTimeout).isBefore(moment()); + } + + return false; +}; + +const isFinalized = (status, translation, lastInteraction, submissionTimeout) => { + return status === taskStatus.Resolved || isIncomplete(status, translation, lastInteraction, submissionTimeout); +}; + +const isPending = status => { + return [taskStatus.Created, taskStatus.Assigned].includes(status); +}; + +const getCurrentPrice = (requesterDeposit, minPrice, maxPrice, lastInteraction, submissionTimeout, status) => { + if (requesterDeposit) return requesterDeposit; + + const currentTime = moment(); + const lastInteractionMoment = moment.unix(lastInteraction); + const submissionTimeoutMoment = moment.unix(submissionTimeout); + let timeSinceLastInteraction = currentTime.subtract(lastInteractionMoment); + + if (timeSinceLastInteraction.isAfter(submissionTimeoutMoment)) return maxPrice; + if (status !== taskStatus.Created) return '0'; + minPrice = BigNumber.from(minPrice); + maxPrice = BigNumber.from(maxPrice); + + timeSinceLastInteraction = BigNumber.from(timeSinceLastInteraction.unix()); + submissionTimeout = BigNumber.from(submissionTimeoutMoment.unix()); + + const currentPrice = minPrice.add(maxPrice.sub(minPrice).mul(timeSinceLastInteraction).div(submissionTimeout)); + return currentPrice.lt(maxPrice) ? currentPrice.toString() : maxPrice.toString(); +}; + +const getCurrentPricePerWord = (currentPrice, wordCount) => { + currentPrice = BigNumber.from(currentPrice); + wordCount = BigNumber.from(wordCount); + + return wordCount > 0 ? currentPrice.div(wordCount).toString() : currentPrice.toString(); +}; + +const getRemainedSubmissionTime = (status, deadline) => { + if (![taskStatus.Created, taskStatus.Assigned].includes(status)) { + return 0; + } + const currentTime = moment(); + const deadlineMoment = moment.unix(deadline); + + const remainingTimeout = deadlineMoment.subtract(currentTime); + return remainingTimeout.unix() > 0 ? remainingTimeout.unix() : 0; +}; + +const getRemainedReviewTime = (status, lastInteraction, reviewTimeout) => { + if (taskStatus.AwaitingReview !== status) { + return 0; + } + const currentTime = moment(); + reviewTimeout = moment.unix(reviewTimeout); + const deadlineMoment = moment.unix(lastInteraction).add(reviewTimeout); + + const remainingTimeout = deadlineMoment.subtract(currentTime); + return remainingTimeout.unix() > 0 ? remainingTimeout.unix() : 0; +}; + +const getCurrentParty = (account, requester, translator, challenger) => { + switch (account) { + /** + * The requester could also be the challenger. + * If that happens, the role he should assume is the one + * of challenger, so he can better informed of next steps. + * That's why challenger is matched before requester here. + */ + case challenger: + return TaskParty.Challenger; + case requester: + return TaskParty.Requester; + case translator: + return TaskParty.Translator; + default: + return TaskParty.Other; + } +}; + +const Task = { + isIncomplete, + isFinalized, + isPending, + getCurrentParty, + getCurrentPrice, + getCurrentPricePerWord, + getRemainedSubmissionTime, + getRemainedReviewTime, +}; +export default Task; From 8a80c95cd41878b7630872e3837385d7e69446e9 Mon Sep 17 00:00:00 2001 From: gratestas Date: Mon, 9 Jan 2023 15:15:58 +0300 Subject: [PATCH 008/132] feat: create dispute helpers --- src/utils/dispute/index.js | 173 +++++++++++++++++++++++++++++++++++++ 1 file changed, 173 insertions(+) create mode 100644 src/utils/dispute/index.js diff --git a/src/utils/dispute/index.js b/src/utils/dispute/index.js new file mode 100644 index 00000000..5404e303 --- /dev/null +++ b/src/utils/dispute/index.js @@ -0,0 +1,173 @@ +import moment from 'moment'; +import { BigNumber } from 'ethers'; +import { DisputeStatus } from '~/features/disputes'; +import { TaskParty } from '~/features/tasks'; +import disputeRuling, { mapRulingToParty } from '~/consts/disputeRuling'; +import { percentage } from '../percentage'; + +const NON_PAYABLE_VALUE = BigNumber.from(2).pow(256).sub(1); + +const isAppealable = status => { + return status === DisputeStatus.Appealable; +}; +const isSolved = status => { + return status === DisputeStatus.Solved; +}; +const isWaiting = status => { + return status === DisputeStatus.Waiting; +}; +const isOtherPary = party => ![TaskParty.Translator, TaskParty.Challenger].includes(party); +const hasWinner = ruling => [disputeRuling.TranslationApproved, disputeRuling.TranslationRejected].includes(ruling); + +const isAppealOngoing = (status, ruling, { remainingTime, hasPaidFees }) => { + if (!isAppealable(status)) return false; + if (!hasWinner(ruling)) { + /** + * If there is no winner, the appeal period is the same for both parties, + * so no matter which one we check here, since both are the same. + */ + return remainingTime[TaskParty.Translator] > 0; + } + const winner = ruling === disputeRuling.TranslationApproved ? TaskParty.Translator : TaskParty.Challenger; + const loser = ruling === disputeRuling.TranslationApproved ? TaskParty.Challenger : TaskParty.Translator; + + const winnerRemainingTime = remainingTime[winner]; + const loserRemainingTime = remainingTime[loser]; + + if (winnerRemainingTime <= 0) return false; + if (winnerRemainingTime > 0 && loserRemainingTime > 0) return true; + + return hasPaidFees[loser] && !hasPaidFees[winner]; +}; + +const getRemainingTimeForAppeal = (status, ruling, appealPeriodStart, appealPeriodEnd, party) => { + if (!isAppealable(status)) return 0; + + const currentTime = moment(); + const appealStartTime = moment.unix(appealPeriodStart); + const appealDeadline = moment.unix(appealPeriodEnd); + + const hasPartyWon = party === mapRulingToParty[ruling]; + const hasJurorsRefusedToRule = ruling === disputeRuling.RefuseToRule; + + if (hasPartyWon || hasJurorsRefusedToRule) { + return Math.max(appealDeadline.subtract(currentTime).unix(), 0); + } + + const appealTimeout = appealDeadline.subtract(appealStartTime); + const losingPartyDeadline = appealStartTime.add(appealTimeout / 2); + const remainingTime = losingPartyDeadline.subtract(currentTime); + + return Math.max(remainingTime.unix(), 0); +}; + +const getRewardPool = (status, ruling, appealCost, rewardPoolParams) => { + if (!isAppealable(status)) { + return { + [TaskParty.Translator]: NON_PAYABLE_VALUE.toString(), + [TaskParty.Challenger]: NON_PAYABLE_VALUE.toString(), + }; + } + appealCost = BigNumber.from(appealCost); + const winnerStakeMultiplier = BigNumber.from(rewardPoolParams.winnerStakeMultiplier); + const loserStakeMultiplier = BigNumber.from(rewardPoolParams.loserStakeMultiplier); + const sharedStakeMultiplier = BigNumber.from(rewardPoolParams.sharedStakeMultiplier); + const multiplierDivisor = BigNumber.from(rewardPoolParams.multiplierDivisor); + + /** + * If there was no winner in the previous round, the arbitration cost is the + * same for both parties and it is calculated using `sharedStakeMultiplier`. + */ + if (ruling === disputeRuling.RefuseToRule) { + const reward = _min(appealCost.mul(sharedStakeMultiplier).div(multiplierDivisor), NON_PAYABLE_VALUE).toString(); + return { + [TaskParty.Translator]: reward, + [TaskParty.Challenger]: reward, + }; + } + const winnerAppealCost = _min( + appealCost.mul(winnerStakeMultiplier).div(multiplierDivisor), + NON_PAYABLE_VALUE + ).toString(); + + const loserAppealCost = _min( + appealCost.mul(loserStakeMultiplier).div(multiplierDivisor), + NON_PAYABLE_VALUE + ).toString(); + + return { + [TaskParty.Translator]: ruling === disputeRuling.TranslationApproved ? winnerAppealCost : loserAppealCost, + [TaskParty.Challenger]: ruling === disputeRuling.TranslationRejected ? winnerAppealCost : loserAppealCost, + }; +}; + +const _min = (value1, value2) => { + if (value1 instanceof BigNumber) { + return value1.lt(value2) ? value1 : value2; + } + return value1 < value2 ? value1 : value2; +}; + +const getTotalAppealCost = (appealCost, rewardPool, party) => { + if (!appealCost) return; + + if (isOtherPary(party)) return NON_PAYABLE_VALUE.toString(); + + appealCost = BigNumber.from(appealCost); + const reward = BigNumber.from(rewardPool[party]) ?? NON_PAYABLE_VALUE; + + return _min(appealCost.add(reward), NON_PAYABLE_VALUE).toString(); +}; + +const getFundingROI = (appealCost, rewardPool, party) => { + if (!appealCost) return; + + if (isOtherPary(party)) return 0; // ZERO.toString(); + + const counterParty = party === TaskParty.Challenger ? TaskParty.Translator : TaskParty.Challenger; + + const partyTotalAppealCost = BigNumber.from(getTotalAppealCost(appealCost, rewardPool, party)); + const counterPartyTotalAppealCost = BigNumber.from(getTotalAppealCost(appealCost, rewardPool, counterParty)); + appealCost = BigNumber.from(appealCost); + + return percentage(counterPartyTotalAppealCost.sub(appealCost), partyTotalAppealCost); +}; + +const getExpectedFinalRuling = (status, ruling, isAppealOngoing, hasPaidFees) => { + if (status !== DisputeStatus.Appealable || isAppealOngoing) { + return ruling; + } + + if (!hasPaidFees[TaskParty.Translator] && !hasPaidFees[TaskParty.Challenger]) { + return ruling; + } + + if (!hasPaidFees[TaskParty.Translator] && hasPaidFees[TaskParty]) { + return disputeRuling.TranslationWasRejected; + } + + if (!hasPaidFees[TaskParty.Challenger] && hasPaidFees[TaskParty.Translator]) { + return disputeRuling.TranslationApproved; + } +}; + +const isWithinAppealPeriod = (appealPeriodStart, appealPeriodEnd) => { + const currentTime = moment(); + appealPeriodStart = moment(appealPeriodStart); + appealPeriodEnd = moment(appealPeriodEnd); + return currentTime.isBetween(appealPeriodStart, appealPeriodEnd); +}; + +const Dispute = { + isSolved, + isWaiting, + isAppealable, + isAppealOngoing, + isWithinAppealPeriod, + getExpectedFinalRuling, + getFundingROI, + getRemainingTimeForAppeal, + getRewardPool, + getTotalAppealCost, +}; +export default Dispute; From e11610824566965d1ee548f9528bdda1dd5b88b0 Mon Sep 17 00:00:00 2001 From: gratestas Date: Mon, 9 Jan 2023 15:25:16 +0300 Subject: [PATCH 009/132] feat: add helper to retrieve contract address by lang pair --- src/utils/getAddressByLanguage.js | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 src/utils/getAddressByLanguage.js diff --git a/src/utils/getAddressByLanguage.js b/src/utils/getAddressByLanguage.js new file mode 100644 index 00000000..4837fa17 --- /dev/null +++ b/src/utils/getAddressByLanguage.js @@ -0,0 +1,8 @@ +import { defaultChainId } from '~/consts/defaultChainId'; + +export const getAddressByLanguageAndChain = (language, chainId) => { + if (!chainId) chainId = defaultChainId; + const contractAddresses = JSON.parse(process.env.LINGUO_CONTRACT_ADDRESSES); + const address = contractAddresses[chainId][language][0]; + return address; +}; From 65b8a946d6be248cb83f7fdbf6572e282b212307 Mon Sep 17 00:00:00 2001 From: gratestas Date: Mon, 9 Jan 2023 15:27:12 +0300 Subject: [PATCH 010/132] refactor: update switchChain helper and change file location --- src/utils/switchChain.js | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 src/utils/switchChain.js diff --git a/src/utils/switchChain.js b/src/utils/switchChain.js new file mode 100644 index 00000000..e08cf079 --- /dev/null +++ b/src/utils/switchChain.js @@ -0,0 +1,37 @@ +import { injected } from '~/connectors/index'; +import { isSupportedChain, SUPPORTED_CHAINS } from '~/consts/supportedChains'; + +const NOT_ADDED_CHAIN_CODE = 4902; + +export const switchChain = async chainId => { + const provider = await injected.getProvider(); + try { + await _switchChain(provider, chainId); + } catch (switchError) { + if (switchError.code === NOT_ADDED_CHAIN_CODE && isSupportedChain(chainId)) { + await addChain(provider, SUPPORTED_CHAINS[chainId.toString()]); + } else throw switchError; + } +}; + +const addChain = async (provider, { chainId, chainName, nativeCurrency, rpcUrls, blockExplorerUrls }) => { + return provider.request({ + method: 'wallet_addEthereumChain', + params: [ + { + chainId: '0x' + chainId.toString(16), + chainName: chainName, + nativeCurrency: nativeCurrency, + rpcUrls: rpcUrls, + blockExplorerUrls: blockExplorerUrls, + }, + ], + }); +}; + +const _switchChain = async (provider, chainId) => { + return provider.request({ + method: 'wallet_switchEthereumChain', + params: [{ chainId: '0x' + chainId.toString(16) }], + }); +}; From 49c4ccb9bc9ee9687076869632736cad8a168e4d Mon Sep 17 00:00:00 2001 From: gratestas Date: Mon, 9 Jan 2023 15:28:25 +0300 Subject: [PATCH 011/132] refactor: change file location --- src/utils/percentage.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 src/utils/percentage.js diff --git a/src/utils/percentage.js b/src/utils/percentage.js new file mode 100644 index 00000000..a0ee44bf --- /dev/null +++ b/src/utils/percentage.js @@ -0,0 +1,15 @@ +import { BigNumber } from 'ethers'; + +export const percentage = (a, b, { decimals = 2 } = {}) => { + const multiplier = BigNumber.from(10).pow(normalizeInput(decimals)); + const result = normalizeInput(a).mul(multiplier).div(normalizeInput(b)); + + return result.toNumber() / 10 ** decimals; +}; + +const normalizeInput = val => { + if (val instanceof BigNumber) { + return val; + } + return BigNumber.from(String(typeof val === 'number' ? Math.trunc(val) : val)); +}; From 445f2a7f9d817b019fbe8069712d343c648bbe69 Mon Sep 17 00:00:00 2001 From: gratestas Date: Mon, 9 Jan 2023 15:29:33 +0300 Subject: [PATCH 012/132] refactor(task-helper): change file location --- src/utils/task/taskStatusToProps.js | 41 +++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 src/utils/task/taskStatusToProps.js diff --git a/src/utils/task/taskStatusToProps.js b/src/utils/task/taskStatusToProps.js new file mode 100644 index 00000000..ede54ce5 --- /dev/null +++ b/src/utils/task/taskStatusToProps.js @@ -0,0 +1,41 @@ +import React from 'react'; +import { Badge } from 'antd'; + +import taskStatus from '~/consts/taskStatus'; +import KlerosLogoOutlined from '~/assets/images/logo-kleros-outlined.svg'; + +export const taskStatusToProps = { + [taskStatus.Created]: { + title: , + colorKey: 'open', + }, + [taskStatus.Assigned]: { + title: , + colorKey: 'inProgress', + }, + [taskStatus.AwaitingReview]: { + title: , + colorKey: 'inReview', + }, + [taskStatus.DisputeCreated]: { + title: ( + <> + + + + ), + colorKey: 'inDispute', + }, + [taskStatus.Resolved]: { + title: , + colorKey: 'finished', + }, + incomplete: { + title: , + colorKey: 'incomplete', + }, +}; From bb42b581f1d5491e9bbf6b7396edf4eae6e13021 Mon Sep 17 00:00:00 2001 From: gratestas Date: Mon, 9 Jan 2023 15:32:07 +0300 Subject: [PATCH 013/132] refactor: refactor publishMetaEvidence helper and change its location --- src/utils/task/publishMetaEvidence.js | 51 +++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 src/utils/task/publishMetaEvidence.js diff --git a/src/utils/task/publishMetaEvidence.js b/src/utils/task/publishMetaEvidence.js new file mode 100644 index 00000000..7e492817 --- /dev/null +++ b/src/utils/task/publishMetaEvidence.js @@ -0,0 +1,51 @@ +import deepmerge from 'deepmerge'; +import metaEvidenceTemplate from '~/assets/fixtures/metaEvidenceTemplate.json'; + +export const publishMetaEvidence = async (chainId, { account, ...metadata }) => { + const evidenceDisplayInterfaceURI = + chainIdToCurrentEvidenceDisplayInterfaceURI[chainId] ?? chainIdToCurrentEvidenceDisplayInterfaceURI[1]; + const dynamicScriptURI = chainIdToCurrentDynamicScriptURI[chainId] ?? chainIdToCurrentDynamicScriptURI[1]; + + const metaEvidence = deepmerge(metaEvidenceTemplate, { + evidenceDisplayInterfaceURI, + dynamicScriptURI, + aliases: { + [account]: 'Requester', + }, + metadata: { + ...metadata, + /** + * v1: + * - Removed `text` field + * - Added `wordCount` field + * - `originalTextFile` is mandatory + */ + __v: '1', + }, + arbitrableChainID: chainId, + /** + * v1.0.0: + * - Removed `text` field + * - Added `wordCount` field + * - `originalTextFile` is mandatory + */ + _v: '1.0.0', + }); + // const { path } = ipfs.publish('linguo-meta-evidence.json', JSON.stringify(metaEvidence)); + const path = '1gfdgd' ?? metaEvidence; // remove this + return path; +}; + +const chainIdToCurrentEvidenceDisplayInterfaceURI = { + 1: '/ipfs/QmXGDMfcxjfQi5SFwpBSb73pPjoZq2N8c6eWCgxx8pVqj7/index.html', + 42: '/ipfs/QmYbtF7K6qCfSYfu2k6nYnVRY8HY97rEAF6mgBWtDgfovw/index.html', + 77: '/ipfs/Qmb5n6PgbshktJqGpwMAxP1moXEPaqq7ZvRufeXXhSPXxW/linguo-evidence-display/index.html', + 100: '/ipfs/Qmb5n6PgbshktJqGpwMAxP1moXEPaqq7ZvRufeXXhSPXxW/linguo-evidence-display/index.html', +}; + +const chainIdToCurrentDynamicScriptURI = { + 1: '/ipfs/QmchWC6L3dT23wwQiJJLWCeS1EDnDYrLcYat93C4Lm4P4E/linguo-dynamic-script.js', + 42: '/ipfs/QmZFcqdsR76jyHyLsBefc4SBuegj2boBDr2skxGauM5DNf/linguo-dynamic-script.js', + 77: '/ipfs/QmPAHCRtSU844fdjNoEws8AgTpzzwsYwMF2wydtpvXAcoZ/linguo-script.js', + 100: '/ipfs/QmPAHCRtSU844fdjNoEws8AgTpzzwsYwMF2wydtpvXAcoZ/linguo-script.js', +}; From ca58adf09f749088ca9057498e0c57eec830ed79 Mon Sep 17 00:00:00 2001 From: gratestas Date: Mon, 9 Jan 2023 15:33:59 +0300 Subject: [PATCH 014/132] feat: add context provider for web3 interaction --- src/context/Web3Provider.jsx | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 src/context/Web3Provider.jsx diff --git a/src/context/Web3Provider.jsx b/src/context/Web3Provider.jsx new file mode 100644 index 00000000..babe1aad --- /dev/null +++ b/src/context/Web3Provider.jsx @@ -0,0 +1,21 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +import { Web3ReactProvider, createWeb3ReactRoot } from '@web3-react/core'; +import { Web3Provider as EthersProvider } from '@ethersproject/providers'; + +const getLibrary = provider => new EthersProvider(provider); +const Web3ProviderNetwork = createWeb3ReactRoot('NETWORK'); + +const Web3Provider = ({ children }) => { + return ( + + {children} + + ); +}; +export default Web3Provider; + +Web3Provider.propTypes = { + children: PropTypes.node.isRequired, +}; From e16f4243a544b0cc76a2bdd9c5dc952e5da7250d Mon Sep 17 00:00:00 2001 From: gratestas Date: Mon, 9 Jan 2023 15:35:57 +0300 Subject: [PATCH 015/132] feat: add context provider for uploading evidences --- src/context/EvidenceUpload.jsx | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 src/context/EvidenceUpload.jsx diff --git a/src/context/EvidenceUpload.jsx b/src/context/EvidenceUpload.jsx new file mode 100644 index 00000000..9e1ef69d --- /dev/null +++ b/src/context/EvidenceUpload.jsx @@ -0,0 +1,21 @@ +import React, { createContext, useContext, useState } from 'react'; +import PropTypes from 'prop-types'; + +const EvidenceUploadContext = createContext(); + +const EvidenceUploadProvider = ({ children }) => { + const [uploadedEvidence, setUploadedEvidence] = useState(); + + return ( + + {children} + + ); +}; + +export const useEvidenceUpload = () => useContext(EvidenceUploadContext); +export default EvidenceUploadProvider; + +EvidenceUploadProvider.propTypes = { + children: PropTypes.node, +}; From b0d908680d1b333e30b84888d780caa450706254 Mon Sep 17 00:00:00 2001 From: gratestas Date: Mon, 9 Jan 2023 15:39:05 +0300 Subject: [PATCH 016/132] refactor: add a component to manage web3 connection state --- src/components/Web3ConnectionManager.jsx | 44 ++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 src/components/Web3ConnectionManager.jsx diff --git a/src/components/Web3ConnectionManager.jsx b/src/components/Web3ConnectionManager.jsx new file mode 100644 index 00000000..237ea53f --- /dev/null +++ b/src/components/Web3ConnectionManager.jsx @@ -0,0 +1,44 @@ +import React, { useEffect } from 'react'; +import t from 'prop-types'; + +import { useWeb3React } from '@web3-react/core'; +import { useInactiveListener, useEagerConnect } from '~/hooks/useWeb3'; +import { network } from '../connectors'; + +const Web3ConnectionManager = ({ children }) => { + const { active } = useWeb3React(); + const { active: networkActive, error: networkError, activate: activateNetwork } = useWeb3React('NETWORK'); + + const triedEager = useEagerConnect(); + + useEffect(() => { + if (triedEager && !networkActive && !networkError && !active) { + activateNetwork(network); + } + }, [triedEager, networkActive, networkError, activateNetwork, active]); + + useEffect(() => { + if (active && networkActive) { + network.pause(); + } + }, [active, networkActive]); + + useEffect(() => { + if (!active && networkActive) { + network.resume(); + } + }, [active, networkActive]); + + useInactiveListener(!triedEager); + + if (!active && !networkActive) { + return
Loading...
; // TODO: add spinner + } + return children; +}; + +export default Web3ConnectionManager; + +Web3ConnectionManager.propTypes = { + children: t.node, +}; From 63852ce60827ece20f8c00df32163c81ec7a27f3 Mon Sep 17 00:00:00 2001 From: gratestas Date: Mon, 9 Jan 2023 15:41:21 +0300 Subject: [PATCH 017/132] refactor: refactor connection button and change its location --- src/components/ConnectionButton.jsx | 49 +++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 src/components/ConnectionButton.jsx diff --git a/src/components/ConnectionButton.jsx b/src/components/ConnectionButton.jsx new file mode 100644 index 00000000..e777b053 --- /dev/null +++ b/src/components/ConnectionButton.jsx @@ -0,0 +1,49 @@ +import React from 'react'; +import styled from 'styled-components'; +import { useWeb3 } from '../hooks/useWeb3'; +import { useConnect } from '../hooks/useConnect'; +import { SUPPORTED_CHAINS } from '../consts/supportedChains'; +import Button from '~/shared/Button'; + +const shortenAddress = address => `${address.slice(0, 6)}...${address.slice(-4)}`; + +const ConnectButton = () => { + const { active } = useWeb3(); + const { activate, connecting } = useConnect(); + return active ? : From a26774508a670479949f9777f429590281c8effe Mon Sep 17 00:00:00 2001 From: gratestas Date: Mon, 9 Jan 2023 16:13:06 +0300 Subject: [PATCH 027/132] refactor: refactor components related to task and translation details --- src/pages/TranslationDetails/TaskDetails.jsx | 96 ++++++---------- .../TranslationDetails/TranslationDetails.jsx | 103 +++++++----------- 2 files changed, 74 insertions(+), 125 deletions(-) diff --git a/src/pages/TranslationDetails/TaskDetails.jsx b/src/pages/TranslationDetails/TaskDetails.jsx index 690170c8..ad4b2117 100644 --- a/src/pages/TranslationDetails/TaskDetails.jsx +++ b/src/pages/TranslationDetails/TaskDetails.jsx @@ -17,71 +17,41 @@ import TaskInfoGrid from '~/features/tasks/TaskInfoGrid'; import TaskPrice from '~/features/tasks/TaskPrice'; import EthFiatValue from '~/features/tokens/EthFiatValue'; import DownloadLink from '~/shared/DownloadLink'; -import { Task, TaskStatus, getFileUrl } from '~/features/tasks'; -import useTask from './useTask'; +import { getFileUrl } from '~/features/tasks'; import TaskStatusDetails from './TaskStatusDetails'; import Evidences from './Evidences'; -import useInterval from '~/shared/useInterval'; import AffixContainer from '~/shared/AffixContainer'; - -const _1_MINUTE_MS = 60 * 1000; +import { useParamsCustom } from '~/hooks/useParamsCustom'; +import { useWeb3 } from '~/hooks/useWeb3'; +import taskStatus from '~/consts/taskStatus'; +import { useTask } from '~/hooks/useTask'; export default function TaskDetails() { - const task = useTask(); - - const { - status, - title, - deadline, - assignedPrice, - expectedQuality, - wordCount, - sourceLanguage, - targetLanguage, - originalTextUrl, - translatedTextUrl, - version, - } = task; - - const deprecatedOriginalText = version > 0 ? undefined : task.text; - const deprecatedOriginalTextFile = version > 0 ? undefined : task.originalTextFile; - const originalTextFileUrl = version > 0 ? task.originalTextFileUrl : undefined; - - const getCurrentPrice = React.useCallback(() => Task.currentPrice(task), [task]); - const [currentPrice, setCurrentPrice] = React.useState(getCurrentPrice); - - const updateCurrentPrice = React.useCallback(() => { - setCurrentPrice(getCurrentPrice()); - }, [getCurrentPrice, setCurrentPrice]); - - const interval = assignedPrice === undefined ? _1_MINUTE_MS : null; - useInterval(updateCurrentPrice, interval); - - const actualPrice = assignedPrice ?? currentPrice; - - const pricePerWord = Task.currentPricePerWord({ - currentPrice: actualPrice, - wordCount, - }); - - const { name = '', requiredLevel = '' } = translationQualityTiers[expectedQuality] || {}; + const { chainId } = useWeb3(); + const { id } = useParamsCustom(chainId); + const { task } = useTask(id); - const showFootnote = status === TaskStatus.Created && !Task.isIncomplete(task); + const deprecatedOriginalText = task.__v > 0 ? undefined : ''; // task.text; + const deprecatedOriginalTextFile = task.__v > 0 ? undefined : task.originalTextFile; + const originalTextFileUrl = task.__v > 0 ? `https://ipfs.kleros.io${task.originalTextFile}` : undefined; + const translatedTextUrl = task.translation ? `https://ipfs.kleros.io${task.translation}` : undefined; + const { name = '', requiredLevel = '' } = translationQualityTiers[task.expectedQuality] || {}; + const showFootnote = task.status === taskStatus.Created && !task.isIncomplete; const taskInfo = [ { title: 'Price per Word', - content: , - footer: `(${formattedValue})`} />, + content: , + footer: `(${formattedValue})`} />, }, { title: 'Word Count', - content: , + content: , }, { title: 'Total Price', - content: , - footer: `(${formattedValue})`} />, + content: , + footer: `(${formattedValue})`} />, }, { title: 'Quality Tier', @@ -91,7 +61,7 @@ export default function TaskDetails() { ]; return ( - `${title} | ${prev}`}> + `${task.title} | ${prev}`}>
- {title} + {task.title} Translation Deadline:{' '} - +
@@ -141,13 +117,13 @@ export default function TaskDetails() {
Source Language - +
Target Language - +
@@ -155,7 +131,7 @@ export default function TaskDetails() { Expected Quality - + @@ -163,7 +139,7 @@ export default function TaskDetails() {
- {translatedTextUrl && ( + {task.translation && (
; + const { status, translation, lasttInteraction, submissionTimeout } = task; + const cardProps = Task.isIncomplete(status, translation, lasttInteraction, submissionTimeout) + ? taskStatusToProps.incomplete + : taskStatusToProps[status]; return ( - - {task => { - const cardProps = Task.isIncomplete(task) ? taskStatusToProps.incomplete : taskStatusToProps[task.status]; - - return ( - `Translation Details | ${title}`}> - } - > - - - - ); - }} - + <> + + `Translation Details | ${title}`}> + } + > + + + + ); } -const taskStatusToProps = { - [TaskStatus.Created]: { - title: , - colorKey: 'open', - }, - [TaskStatus.Assigned]: { - title: , - colorKey: 'inProgress', - }, - [TaskStatus.AwaitingReview]: { - title: , - colorKey: 'inReview', - }, - [TaskStatus.DisputeCreated]: { - title: ( - <> - - - - ), - colorKey: 'inDispute', - }, - [TaskStatus.Resolved]: { - title: , - colorKey: 'finished', - }, - incomplete: { - title: , - colorKey: 'incomplete', - }, -}; - const StyledSingleCardLayout = styled(SingleCardLayout)` .card-header { background-color: ${p => p.theme.hexToRgba(p.theme.color.status[p.$colorKey], '0.06')}; From 9c673be0aea4e4d231a763624f3cdcb0c5c8aefe Mon Sep 17 00:00:00 2001 From: gratestas Date: Mon, 9 Jan 2023 16:21:00 +0300 Subject: [PATCH 028/132] refactor: refactor 'TranslationRequest' related components --- .../LanguagesSelectionFields.jsx | 1 - .../TranslationRequest/TranslationRequest.jsx | 18 ++++---- .../TranslationRequestForm.jsx | 42 +++++++++++-------- 3 files changed, 33 insertions(+), 28 deletions(-) diff --git a/src/pages/TranslationRequest/LanguagesSelectionFields.jsx b/src/pages/TranslationRequest/LanguagesSelectionFields.jsx index 85c2a3a5..abd7993a 100644 --- a/src/pages/TranslationRequest/LanguagesSelectionFields.jsx +++ b/src/pages/TranslationRequest/LanguagesSelectionFields.jsx @@ -15,7 +15,6 @@ export default function LanguagesSelectionFields({ setFieldsValue }) { source: undefined, target: undefined, }); - const handleSourceLanguageChange = value => { setSelectedLanguages(current => ({ ...current, source: value })); }; diff --git a/src/pages/TranslationRequest/TranslationRequest.jsx b/src/pages/TranslationRequest/TranslationRequest.jsx index 7b20b6ac..c6d199b6 100644 --- a/src/pages/TranslationRequest/TranslationRequest.jsx +++ b/src/pages/TranslationRequest/TranslationRequest.jsx @@ -1,22 +1,22 @@ import React from 'react'; -import { useDispatch, useSelector } from 'react-redux'; import { Titled } from 'react-titled'; import { Alert } from '~/adapters/antd'; -import { getNetworkName } from '~/features/web3'; + import RequiredWalletGateway from '~/features/web3/RequiredWalletGateway'; -import { getCounterPartyChainId, isSupportedSideChain } from '~/features/web3/supportedChains'; -import { selectAccount, selectChainId, switchChain } from '~/features/web3/web3Slice'; import Button from '~/shared/Button'; import ContentBlocker from '~/shared/ContentBlocker'; import Spacer from '~/shared/Spacer'; import WithRouteMessage from '~/shared/WithRouteMessage'; + +import { useWeb3 } from '~/hooks/useWeb3'; +import { getCounterPartyChainId, getNetworkName, isSupportedSideChain } from '~/consts/supportedChains'; +import { switchChain } from '~/utils/switchChain'; + import SingleCardLayout from '../layouts/SingleCardLayout'; import TranslationRequestForm from './TranslationRequestForm'; function TranslationRequest() { - const dispatch = useDispatch(); - const account = useSelector(selectAccount); - const chainId = useSelector(selectChainId); + const { account, chainId } = useWeb3(); const counterPartyChainId = getCounterPartyChainId(chainId); const formBlocked = !account || !isSupportedSideChain(chainId); @@ -28,13 +28,13 @@ function TranslationRequest() {

Currently it is not possible to request new translations on {getNetworkName(chainId)}.

Get more affordable gas fees:{' '} - . diff --git a/src/pages/TranslationRequest/TranslationRequestForm.jsx b/src/pages/TranslationRequest/TranslationRequestForm.jsx index 07b34dc3..5d092739 100644 --- a/src/pages/TranslationRequest/TranslationRequestForm.jsx +++ b/src/pages/TranslationRequest/TranslationRequestForm.jsx @@ -1,13 +1,10 @@ -import React from 'react'; -import { useDispatch, useSelector } from 'react-redux'; +import React, { useEffect } from 'react'; import styled from 'styled-components'; import { LoadingOutlined } from '@ant-design/icons'; import { Form, Row, Steps } from 'antd'; import dayjs from 'dayjs'; import utc from 'dayjs/plugin/utc'; import translationQualityTiers from '~/assets/fixtures/translationQualityTiers.json'; -import { create as createTask } from '~/features/tasks/tasksSlice'; -import { selectAccount } from '~/features/web3/web3Slice'; import { normalizeBaseUnit } from '~/features/tokens'; import Button from '~/shared/Button'; import Spacer from '~/shared/Spacer'; @@ -19,23 +16,38 @@ import LanguagesSelectionFields from './LanguagesSelectionFields'; import OriginalSourceFields from './OriginalSourceFields'; import PriceDefinitionFields from './PriceDefinitionFields'; import TitleField from './TitleField'; +import { useWeb3 } from '~/hooks/useWeb3'; +import { useLinguoApi } from '~/hooks/useLinguo'; +import { getAddressByLanguageAndChain } from '~/utils/getAddressByLanguage'; +import { publishMetaEvidence } from '~/utils/task/publishMetaEvidence'; +import moment from 'moment'; dayjs.extend(utc); function TranslationRequestForm() { - const dispatch = useDispatch(); + const { chainId } = useWeb3(); + const { createTask, setAddress } = useLinguoApi(); const [form] = Form.useForm(); const [state, send] = useStateMachine(formStateMachine); - const account = useSelector(selectAccount); + + const sourceLanguage = form.getFieldValue('sourceLanguage'); + const targetLanguage = form.getFieldValue('targetLanguage'); + const getLang = langCode => langCode.split('-')[0]; + + useEffect(() => { + if (!sourceLanguage && !targetLanguage) return; + + const lang = `${getLang(sourceLanguage)}|${getLang(targetLanguage)}`; + const address = getAddressByLanguageAndChain(lang, chainId); + setAddress(address); + }, [chainId, setAddress, sourceLanguage, targetLanguage]); const handleFinish = React.useCallback( async values => { const { originalTextFile, deadline, minPriceNumeric, maxPriceNumeric, ...rest } = values; - send('SUBMIT'); - const data = { + const metadata = { ...rest, - account, minPrice: safeToWei(minPriceNumeric), maxPrice: safeToWei(maxPriceNumeric), deadline: new Date(deadline).toISOString(), @@ -43,19 +55,13 @@ function TranslationRequestForm() { }; try { - await dispatch( - createTask(data, { - meta: { - thunk: true, - redirect: true, - }, - }) - ); + const metaEvidence = await publishMetaEvidence(chainId, metadata); + await createTask(moment(metadata.deadline).unix(), metadata.minPrice, metaEvidence, metadata.maxPrice); } finally { send('RESET'); } }, - [dispatch, account, send] + [send, chainId, createTask] ); const handleFinishFailed = React.useCallback( From 90cdae8ba20208a85bc08c96430b6b36a8f1454a Mon Sep 17 00:00:00 2001 From: gratestas Date: Mon, 9 Jan 2023 16:23:25 +0300 Subject: [PATCH 029/132] refactor: temporarily set custom values for testing purpose --- src/features/web3/RequiredWalletGateway.jsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/features/web3/RequiredWalletGateway.jsx b/src/features/web3/RequiredWalletGateway.jsx index 551d822b..a53b351f 100644 --- a/src/features/web3/RequiredWalletGateway.jsx +++ b/src/features/web3/RequiredWalletGateway.jsx @@ -1,16 +1,15 @@ import React from 'react'; import t from 'prop-types'; -import { useSelector } from 'react-redux'; import styled from 'styled-components'; import { Alert } from '~/adapters/antd'; import WalletConnectionModal from './WalletConnectionModal'; import RequiredWeb3Gateway from './RequiredWeb3Gateway'; -import { selectIsConnected, selectIsConnecting, selectAccount } from './web3Slice'; +import { useWeb3 } from '~/hooks/useWeb3'; function RequiredWalletGateway({ message, children, missing, error, render, renderMissing, renderError }) { - const isConnected = useSelector(selectIsConnected); - const isConnecting = useSelector(selectIsConnecting); - const account = useSelector(selectAccount); + const { account } = useWeb3(); + const isConnecting = false; + const isConnected = true; const hasAccount = isConnected && !!account; const missingWalletWarning = ; From 51c1a766dd3065a4ab5d486323977c7c80d709e6 Mon Sep 17 00:00:00 2001 From: gratestas Date: Mon, 9 Jan 2023 16:25:40 +0300 Subject: [PATCH 030/132] refactor: refactor components related to displaying task data --- src/features/tasks/TaskCard.jsx | 144 ++++++++++--------- src/features/tasks/TaskCardFooter.jsx | 86 +++++++---- src/features/tasks/TaskInteractionButton.jsx | 84 ++--------- 3 files changed, 145 insertions(+), 169 deletions(-) diff --git a/src/features/tasks/TaskCard.jsx b/src/features/tasks/TaskCard.jsx index 7bea7061..6d800fcc 100644 --- a/src/features/tasks/TaskCard.jsx +++ b/src/features/tasks/TaskCard.jsx @@ -1,50 +1,73 @@ import React from 'react'; import t from 'prop-types'; import styled from 'styled-components'; -import { Badge, Tooltip, Typography } from 'antd'; -import { useShallowEqualSelector } from '~/adapters/react-redux'; +import { Tooltip, Typography } from 'antd'; + import * as r from '~/app/routes'; -import translationQualityTiers from '~/assets/fixtures/translationQualityTiers.json'; import Card from '~/shared/Card'; import FormattedNumber from '~/shared/FormattedNumber'; import useInterval from '~/shared/useInterval'; import Spacer from '~/shared/Spacer'; -import { Task, TaskStatus } from '~/features/tasks'; import EthFiatValue from '~/features/tokens/EthFiatValue'; -import KlerosLogoOutlined from '~/assets/images/logo-kleros-outlined.svg'; + import TaskCardFooter from './TaskCardFooter'; import TaskInfoGrid from './TaskInfoGrid'; import TaskLanguages from './TaskLanguages'; import TaskPrice from './TaskPrice'; -import { selectById } from './tasksSlice'; -const getTaskDetailsRoute = r.withParamSubtitution(r.TRANSLATION_TASK_DETAILS); +import translationQualityTiers from '~/assets/fixtures/translationQualityTiers.json'; -const _1_MINUTE_MS = 60 * 1000; +import Task from '~/utils/task'; +import taskStatus from '~/consts/taskStatus'; +import { _1_MINUTE_MS } from '~/consts/time'; +import { taskStatusToProps } from '~/utils/task/taskStatusToProps'; +import { getAddressByLanguageAndChain } from '~/utils/getAddressByLanguage'; +import { useWeb3 } from '~/hooks/useWeb3'; -export default function TaskCard({ id, footerProps }) { - const task = useShallowEqualSelector(selectById(id)); - const { status, title, assignedPrice, sourceLanguage, targetLanguage, wordCount, expectedQuality } = task; +const getTaskDetailsRoute = r.withParamSubtitution(r.TRANSLATION_TASK_DETAILS); + +export default function TaskCard({ data, metadata, footerProps }) { + const { chainId } = useWeb3(); + const { title, sourceLanguage, targetLanguage, wordCount, expectedQuality } = metadata; + const { + minPrice, + maxPrice, + lang, + lastInteraction, + requesterDeposit, + submissionTimeout, + status, + taskID, + translation, + } = data; - const getCurrentPrice = React.useCallback(() => Task.currentPrice(task), [task]); - const [currentPrice, setCurrentPrice] = React.useState(getCurrentPrice); + const [currentPrice, setCurrentPrice] = React.useState( + Task.getCurrentPrice(requesterDeposit, minPrice, maxPrice, lastInteraction, submissionTimeout, status) + ); const updateCurrentPrice = React.useCallback(() => { - setCurrentPrice(getCurrentPrice()); - }, [getCurrentPrice, setCurrentPrice]); + if (requesterDeposit) { + setCurrentPrice(requesterDeposit); + } else { + const value = Task.getCurrentPrice( + requesterDeposit, + minPrice, + maxPrice, + lastInteraction, + submissionTimeout, + status + ); + setCurrentPrice(value); + } + }, [lastInteraction, maxPrice, minPrice, status, requesterDeposit, submissionTimeout]); - const interval = assignedPrice === undefined ? _1_MINUTE_MS : null; + const interval = requesterDeposit === undefined ? _1_MINUTE_MS : null; useInterval(updateCurrentPrice, interval); - const actualPrice = assignedPrice ?? currentPrice; - - const pricePerWord = Task.currentPricePerWord({ - currentPrice: actualPrice, - wordCount, - }); - + const actualPrice = status === taskStatus.Assigned ? requesterDeposit : currentPrice; + const pricePerWord = Task.getCurrentPricePerWord(actualPrice, wordCount); const { name = '', requiredLevel = '' } = translationQualityTiers[expectedQuality] || {}; - + const isIncomplete = Task.isIncomplete(status, translation, lastInteraction, submissionTimeout); const taskInfo = [ { title: 'Price per Word', @@ -58,11 +81,7 @@ export default function TaskCard({ id, footerProps }) { { title: 'Total Price', content: ( - + ), footer: `(${formattedValue})`} />, }, @@ -73,22 +92,22 @@ export default function TaskCard({ id, footerProps }) { }, ]; - const cardProps = Task.isIncomplete(task) ? taskStatusToProps.incomplete : taskStatusToProps[status]; + const cardProps = isIncomplete ? taskStatusToProps.incomplete : taskStatusToProps[status]; + + const address = getAddressByLanguageAndChain(lang, chainId); + const url = getTaskDetailsRoute({ id: `${address}/${taskID}` }); - const url = getTaskDetailsRoute({ id }); return ( } + footer={} > See More - {/* The wrapping div fixes an issue with styled compnents not - properly performing ref forwarding for function components. */}

, - colorKey: 'open', - }, - [TaskStatus.Assigned]: { - title: , - colorKey: 'inProgress', - }, - [TaskStatus.AwaitingReview]: { - title: , - colorKey: 'inReview', - }, - [TaskStatus.DisputeCreated]: { - title: ( - <> - - - - ), - colorKey: 'inDispute', - }, - [TaskStatus.Resolved]: { - title: , - colorKey: 'finished', - }, - incomplete: { - title: , - colorKey: 'incomplete', - }, -}; - const MainLink = styled.a` display: block; text-indent: -9999px; diff --git a/src/features/tasks/TaskCardFooter.jsx b/src/features/tasks/TaskCardFooter.jsx index 3ebc3c94..60b1d664 100644 --- a/src/features/tasks/TaskCardFooter.jsx +++ b/src/features/tasks/TaskCardFooter.jsx @@ -1,23 +1,21 @@ -import React from 'react'; +import React, { useState, useEffect } from 'react'; import t from 'prop-types'; -import { useSelector } from 'react-redux'; import styled from 'styled-components'; import { Col, Row } from 'antd'; -import { useShallowEqualSelector } from '~/adapters/react-redux'; -import RemainingTime from '~/shared/RemainingTime'; -import { Task, TaskStatus } from '~/features/tasks'; -import { selectAccount } from '~/features/web3/web3Slice'; -import TaskParty from './entities/TaskParty'; -import { selectById } from './tasksSlice'; + import TaskInteractionButton from './TaskInteractionButton'; +import RemainingTime from '~/shared/RemainingTime'; -export default function TaskCardFooter({ id, rightSideContent }) { - const task = useShallowEqualSelector(selectById(id)); +import { useWeb3 } from '~/hooks/useWeb3'; +import { getReviewTimeout } from '~/hooks/useLinguo'; +import Task from '~/utils/task'; +import taskStatus from '~/consts/taskStatus'; +export default function TaskCardFooter({ data, contractAddress, rightSideContent }) { return ( - + {rightSideContent} @@ -25,7 +23,17 @@ export default function TaskCardFooter({ id, rightSideContent }) { } TaskCardFooter.propTypes = { - id: t.oneOfType([t.number, t.string]).isRequired, + contractAddress: t.string.isRequired, + data: t.shape({ + lastInteraction: t.string.isRequired, + minPrice: t.string.isRequired, + maxPrice: t.string.isRequired, + requesterDeposit: t.string.isRequired, + submissionTimeout: t.string.isRequired, + status: t.oneOf(Object.values(taskStatus)).isRequired, + taskID: t.string.isRequired, + translation: t.string.isRequired, + }), rightSideContent: t.node, }; @@ -33,17 +41,42 @@ TaskCardFooter.defaultProps = { rightSideContent: null, }; -function LeftSideContent(task) { - const { id, status } = task; - const account = useSelector(selectAccount); +LeftSideContent.propTypes = { + contractAddress: t.string.isRequired, + data: t.shape({ + deadline: t.string.isRequired, + lastInteraction: t.string.isRequired, + requester: t.string.isRequired, + submissionTimeout: t.string.isRequired, + status: t.oneOf(Object.values(taskStatus)).isRequired, + taskID: t.string.isRequired, + translation: t.string.isRequired, + }), + rightSideContent: t.node, +}; + +function LeftSideContent({ data, contractAddress }) { + const [reviewTimeout, setReviewTimeout] = useState(0); + + const { deadline, lastInteraction, requester, taskID, translation, status, submissionTimeout } = data; + const { account, library } = useWeb3(); + const _isIncomplete = Task.isIncomplete(status, translation, lastInteraction, submissionTimeout); + + useEffect(() => { + const fetchReviewTimeout = async () => { + const timeout = await getReviewTimeout(contractAddress, library); + setReviewTimeout(timeout); + }; + fetchReviewTimeout(); + }, [contractAddress, library]); const TaskFooterInfoPending = () => { - if (Task.isIncomplete(task)) { - const isRequester = task.parties[TaskParty.Requester] === account; + if (_isIncomplete) { + const isRequester = requester === account; return isRequester ? ( ) : null; } - - const currentDate = new Date(); - const timeout = Task.remainingTimeForSubmission(task, { currentDate }); + const timeout = Task.getRemainedSubmissionTime(status, deadline); return ( { - const currentDate = new Date(); - const timeout = Task.remainingTimeForReview(task, { currentDate }); + const timeout = Task.getRemainedReviewTime(status, lastInteraction, reviewTimeout.toString()); return timeout > 0 ? ( null, - [TaskStatus.Resolved]: () => null, + [taskStatus.Created]: TaskFooterInfoPending, + [taskStatus.Assigned]: TaskFooterInfoPending, + [taskStatus.AwaitingReview]: TaskFooterInfoAwaitingReview, + [taskStatus.DisputeCreated]: () => null, + [taskStatus.Resolved]: () => null, }; const Component = taskFooterInfoByStatusMap[status]; diff --git a/src/features/tasks/TaskInteractionButton.jsx b/src/features/tasks/TaskInteractionButton.jsx index c5b0e393..a3085f50 100644 --- a/src/features/tasks/TaskInteractionButton.jsx +++ b/src/features/tasks/TaskInteractionButton.jsx @@ -1,57 +1,30 @@ import React from 'react'; import t from 'prop-types'; -import { useDispatch, useSelector } from 'react-redux'; import { CheckOutlined, LoadingOutlined, SendOutlined } from '@ant-design/icons'; import Button from '~/shared/Button'; -import { selectAccount } from '~/features/web3/web3Slice'; -import useStateMachine from '~/shared/useStateMachine'; -import { approveTranslation, assignTranslator, reimburseRequester, withdrawAllFeesAndRewards } from './tasksSlice'; +import { useLinguoApi } from '~/hooks/useLinguo'; +import { useTransactionState } from '~/hooks/useTransactionState'; -export default function TaskInteractionButton({ id, interaction, content, buttonProps, onSuccess, onFailure }) { - const dispatch = useDispatch(); - const account = useSelector(selectAccount); +export default function TaskInteractionButton({ id, interaction, content, buttonProps }) { + const linguo = useLinguoApi(); - const [state, send] = useStateMachine(buttonStateMachine); - const actionCreator = interactionToActionCreator[interaction]; + const interactionToActionMap = { + [TaskInteraction.Assign]: () => linguo.assignTask(id), + [TaskInteraction.Accept]: () => linguo.acceptTranslation(id), + [TaskInteraction.Reimburse]: () => linguo.reimburseRequester(id), + [TaskInteraction.Withdraw]: () => linguo.withdrawAllFeesAndRewards(id), + }; - const action = React.useMemo( - () => - actionCreator( - { id, account }, - { - meta: { - tx: { wait: 0 }, - thunk: { id }, - }, - } - ), - [actionCreator, id, account] - ); - - const handleClick = React.useCallback( - async evt => { - evt.preventDefault(); + const taskAction = interactionToActionMap[interaction]; - send('START'); - try { - const result = await dispatch(action); - onSuccess(result); - send('SUCCESS'); - } catch (err) { - console.warn('Failed to submit:', err); - onFailure(err); - send('ERROR'); - } - }, - [dispatch, action, send, onSuccess, onFailure] - ); + const { state, handleTransaction } = useTransactionState(taskAction); const disabled = state !== 'idle'; const icon = content[state] ? content[state].icon ?? null : defaultButtonContent[state].icon; const text = content[state]?.text ?? defaultButtonContent[state].text; return ( - ); @@ -64,7 +37,7 @@ const contentItemShape = t.shape({ const TaskInteraction = { Assign: 'assign', - Approve: 'approve', + Accept: 'accept', Reimburse: 'reimburse', Withdraw: 'withdraw', }; @@ -85,39 +58,10 @@ TaskInteractionButton.propTypes = { TaskInteractionButton.defaultProps = { buttonProps: {}, content: {}, - onSuccess: () => {}, - onFailure: () => {}, }; TaskInteractionButton.Interaction = TaskInteraction; -const buttonStateMachine = { - initial: 'idle', - states: { - idle: { - on: { - START: 'pending', - }, - }, - pending: { - on: { - SUCCESS: 'succeeded', - ERROR: 'idle', - }, - }, - succeeded: { - final: true, - }, - }, -}; - -const interactionToActionCreator = { - [TaskInteraction.Assign]: assignTranslator, - [TaskInteraction.Approve]: approveTranslation, - [TaskInteraction.Reimburse]: reimburseRequester, - [TaskInteraction.Withdraw]: withdrawAllFeesAndRewards, -}; - const defaultButtonContent = { idle: { text: 'Send', From 9d1191e6eb525126ab18a2950402d6bcbf60aa8c Mon Sep 17 00:00:00 2001 From: gratestas Date: Mon, 9 Jan 2023 16:26:34 +0300 Subject: [PATCH 031/132] refactor: refactor TaskList component in translator dashboard --- src/features/translator/TaskList.jsx | 48 +++++++++++++++------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/src/features/translator/TaskList.jsx b/src/features/translator/TaskList.jsx index 5ffcaf00..87367c92 100644 --- a/src/features/translator/TaskList.jsx +++ b/src/features/translator/TaskList.jsx @@ -4,10 +4,11 @@ import styled from 'styled-components'; import { Col, Row, Tooltip, Typography } from 'antd'; import Spacer from '~/shared/Spacer'; import TaskCard from '~/features/tasks/TaskCard'; -import { TaskStatus } from '~/features/tasks'; import { useShallowEqualSelector } from '~/adapters/react-redux'; import { TaskCardFooterInfoDisplay } from '../tasks/TaskCardFooter'; import { selectAllSkills } from './translatorSlice'; +import { useIPFSQuery } from '~/hooks/queries/useIPFSQuery'; +import taskStatus from '~/consts/taskStatus'; export default function TaskList({ data, showFootnote }) { return data.length === 0 ? ( @@ -64,44 +65,45 @@ const minimumLevelByQuality = { }; function TranslatorTaskCard(props) { - const { sourceLanguage, targetLanguage, expectedQuality } = props; - const minimumLevel = minimumLevelByQuality[expectedQuality]; + const { data } = useIPFSQuery(props.metaEvidence.URI); + const metadata = data?.metadata; + const minimumLevel = minimumLevelByQuality[metadata?.expectedQuality]; const skills = useShallowEqualSelector(selectAllSkills); const hasSkill = React.useMemo(() => { const hasSourceLanguageSkill = skills.some( - ({ language, level }) => sourceLanguage === language && level >= minimumLevel + ({ language, level }) => metadata?.sourceLanguage === language && level >= minimumLevel ); const hasTargetLanguageSkill = skills.some( - ({ language, level }) => targetLanguage === language && level >= minimumLevel + ({ language, level }) => metadata?.targetLanguage === language && level >= minimumLevel ); return hasSourceLanguageSkill && hasTargetLanguageSkill; - }, [targetLanguage, sourceLanguage, minimumLevel, skills]); - + }, [metadata?.targetLanguage, metadata?.sourceLanguage, minimumLevel, skills]); return ( - - ), - }} - /> + {metadata && ( + + ), + }} + /> + )} ); } TranslatorTaskCard.propTypes = { - status: t.oneOf(Object.values(TaskStatus)).isRequired, - sourceLanguage: t.string.isRequired, - targetLanguage: t.string.isRequired, - expectedQuality: t.string.isRequired, + metaEvidence: t.shape({ URI: t.string.isRequired }), + status: t.oneOf(Object.values(taskStatus)).isRequired, }; const pluralize = (quantity, { single, many }) => (quantity === 1 ? single : many); From 43b1cce71cf211129777a1ebac804a83402a869e Mon Sep 17 00:00:00 2001 From: gratestas Date: Mon, 9 Jan 2023 16:27:54 +0300 Subject: [PATCH 032/132] refactor: add updates to TranslationDashboard components --- .../TranslatorDashboard/TaskListFetcher.jsx | 32 +++---------------- .../TranslatorDashboard/TaskListHeader.jsx | 5 ++- 2 files changed, 7 insertions(+), 30 deletions(-) diff --git a/src/pages/TranslatorDashboard/TaskListFetcher.jsx b/src/pages/TranslatorDashboard/TaskListFetcher.jsx index b4885dd5..185b4eab 100644 --- a/src/pages/TranslatorDashboard/TaskListFetcher.jsx +++ b/src/pages/TranslatorDashboard/TaskListFetcher.jsx @@ -1,48 +1,26 @@ import React from 'react'; -import { useDispatch, useSelector } from 'react-redux'; import { Link } from 'react-router-dom'; import styled from 'styled-components'; -import { useShallowEqualSelector } from '~/adapters/react-redux'; import { Spin } from '~/adapters/antd'; import * as r from '~/app/routes'; import TaskList from '~/features/translator/TaskList'; import { statusFilters, useFilters } from '~/features/translator'; -import { - fetchTasks, - selectTasksForCurrentFilter, - selectAllSkills, - selectIsLoading, -} from '~/features/translator/translatorSlice'; import DismissableAlert from '~/features/ui/DismissableAlert'; -import { selectAccount, selectChainId } from '~/features/web3/web3Slice'; import TopLoadingBar from '~/shared/TopLoadingBar'; +import { useTasksQuery } from '~/hooks/queries/useTasksQuery'; export default function TaskListFetcher() { - const dispatch = useDispatch(); - const account = useSelector(selectAccount); - const chainId = useSelector(selectChainId); - const isLoading = useSelector(state => selectIsLoading(state, { account, chainId })); - const skills = useShallowEqualSelector(selectAllSkills); - - const doFetchTasks = React.useCallback(() => { - dispatch(fetchTasks({ account, chainId })); - }, [dispatch, account, chainId]); - - React.useEffect(() => { - doFetchTasks(); - }, [doFetchTasks]); - const [filters] = useFilters(); - const data = useShallowEqualSelector(state => selectTasksForCurrentFilter(state, { account, chainId, skills })); - const showFootnote = [statusFilters.open].includes(filters.status) && data.length > 0; + const { tasks, isLoading } = useTasksQuery(0); + const showFootnote = [statusFilters.open].includes(filters.status) && tasks !== undefined; return ( <> - + {filterDescriptionMap[getFilterTreeName({ status, allTasks: filters.allTasks })]} - + {tasks && } ); diff --git a/src/pages/TranslatorDashboard/TaskListHeader.jsx b/src/pages/TranslatorDashboard/TaskListHeader.jsx index b39344f2..4e10e181 100644 --- a/src/pages/TranslatorDashboard/TaskListHeader.jsx +++ b/src/pages/TranslatorDashboard/TaskListHeader.jsx @@ -7,8 +7,7 @@ import TaskStatusFilter from '~/features/tasks/TaskStatusFilter'; import TaskOwnershipFilter from '~/features/translator/TaskOwnershipFilter'; import { statusFilters, useFilters } from '~/features/translator'; import Button from '~/shared/Button'; -import { useSelector } from 'react-redux'; -import { selectAccount } from '~/features/web3/web3Slice'; +import { useWeb3 } from '~/hooks/useWeb3'; export default function TaskListHeader() { return ( @@ -35,7 +34,7 @@ export default function TaskListHeader() { } function TaskOwnershipFilterContainer() { - const account = useSelector(selectAccount); + const { account } = useWeb3(); const [{ status, allTasks }, setFilters] = useFilters(); const handleFilterChange = React.useCallback( From 18773a36cab3f446488754010420e2e396876358 Mon Sep 17 00:00:00 2001 From: gratestas Date: Mon, 9 Jan 2023 21:50:43 +0300 Subject: [PATCH 033/132] refactor: change the location of status filters --- src/consts/statusFilters.js | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/consts/statusFilters.js diff --git a/src/consts/statusFilters.js b/src/consts/statusFilters.js new file mode 100644 index 00000000..ee39ed1d --- /dev/null +++ b/src/consts/statusFilters.js @@ -0,0 +1,9 @@ +export const statusFilters = Object.freeze({ + all: 'all', + open: 'open', + inProgress: 'inProgress', + inReview: 'inReview', + inDispute: 'inDispute', + finished: 'finished', + incomplete: 'incomplete', +}); From 1f5d86e93a0b84e2290d4c45fc0f0082a24e367b Mon Sep 17 00:00:00 2001 From: gratestas Date: Mon, 9 Jan 2023 21:53:32 +0300 Subject: [PATCH 034/132] refactor: get rid of hardcoded string values --- src/features/tasks/TaskStatusFilter.jsx | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/features/tasks/TaskStatusFilter.jsx b/src/features/tasks/TaskStatusFilter.jsx index 660427cf..c018b9f1 100644 --- a/src/features/tasks/TaskStatusFilter.jsx +++ b/src/features/tasks/TaskStatusFilter.jsx @@ -3,6 +3,7 @@ import t from 'prop-types'; import styled from 'styled-components'; import { Badge, Select } from 'antd'; import theme from '~/features/ui/theme'; +import { statusFilters } from '~/consts/statusFilters'; export default function TaskStatusFilter({ fullWidth, onChange, value, defaultValue }) { const controlledProps = value !== undefined ? { value } : {}; @@ -31,37 +32,37 @@ TaskStatusFilter.propTypes = { TaskStatusFilter.defaultProps = { onChange: () => {}, - defaultValue: 'all', + defaultValue: statusFilters.all, fullWidth: false, }; const filterOptions = [ { - value: 'all', + value: statusFilters.all, label: , }, { - value: 'open', + value: statusFilters.open, label: , }, { - value: 'inProgress', + value: statusFilters.inProgress, label: , }, { - value: 'inReview', + value: statusFilters.inReview, label: , }, { - value: 'inDispute', + value: statusFilters.inDispute, label: , }, { - value: 'finished', + value: statusFilters.finished, label: , }, { - value: 'incomplete', + value: statusFilters.incomplete, label: , }, ]; From d44f14765676a1be08962676054f2b24087ee793 Mon Sep 17 00:00:00 2001 From: gratestas Date: Mon, 9 Jan 2023 21:56:30 +0300 Subject: [PATCH 035/132] refactor: use new implementation of filters --- .../TranslatorDashboard/TaskListHeader.jsx | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/pages/TranslatorDashboard/TaskListHeader.jsx b/src/pages/TranslatorDashboard/TaskListHeader.jsx index 4e10e181..ac2ffa18 100644 --- a/src/pages/TranslatorDashboard/TaskListHeader.jsx +++ b/src/pages/TranslatorDashboard/TaskListHeader.jsx @@ -5,9 +5,11 @@ import * as r from '~/app/routes'; import TranslatorAvatar from '~/assets/images/avatar-work-as-a-translator.svg'; import TaskStatusFilter from '~/features/tasks/TaskStatusFilter'; import TaskOwnershipFilter from '~/features/translator/TaskOwnershipFilter'; -import { statusFilters, useFilters } from '~/features/translator'; import Button from '~/shared/Button'; + import { useWeb3 } from '~/hooks/useWeb3'; +import { useTasksFilter } from '~/context/TasksFilterProvider'; +import { statusFilters } from '~/consts/statusFilters'; export default function TaskListHeader() { return ( @@ -35,13 +37,17 @@ export default function TaskListHeader() { function TaskOwnershipFilterContainer() { const { account } = useWeb3(); - const [{ status, allTasks }, setFilters] = useFilters(); + const { + filters: { status, allTasks }, + updateFilters, + } = useTasksFilter(); + console.log({ updateFilters }); const handleFilterChange = React.useCallback( value => { - setFilters({ status, allTasks: value }); + updateFilters({ status, allTasks: value }); }, - [setFilters, status] + [status, updateFilters] ); const shouldDisplay = account && status !== statusFilters.open; @@ -50,13 +56,16 @@ function TaskOwnershipFilterContainer() { } function TaskStatusFilterContainer() { - const [{ status, allTasks }, setFilters] = useFilters(); + const { + filters: { status, allTasks }, + updateFilters, + } = useTasksFilter(); const handleFilterChange = React.useCallback( value => { - setFilters({ status: value, allTasks }); + updateFilters({ status: value, allTasks }); }, - [setFilters, allTasks] + [updateFilters, allTasks] ); return ; From 40d195d6a1d411857b54e0df93c4cedb68b4f13c Mon Sep 17 00:00:00 2001 From: gratestas Date: Tue, 10 Jan 2023 11:35:36 +0300 Subject: [PATCH 036/132] refactor: create helper to filter tasks in Translator page --- src/utils/getTasksByFilters.js | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 src/utils/getTasksByFilters.js diff --git a/src/utils/getTasksByFilters.js b/src/utils/getTasksByFilters.js new file mode 100644 index 00000000..715f45b2 --- /dev/null +++ b/src/utils/getTasksByFilters.js @@ -0,0 +1,28 @@ +import { statusFilters } from '~/consts/statusFilters'; +import Task from '~/utils/task/index'; +import taskStatus from '~/consts/taskStatus'; + +export const getTasksByFilters = (tasks, account, filters) => { + const { status, allTasks } = filters; + const filteredTasks = tasks.filter(task => { + if (!filterToTaskStatusMap[status](task)) return false; + if (!allTasks && task.translator !== account) return false; + return true; + }); + + return filteredTasks; +}; + +const filterToTaskStatusMap = { + [statusFilters.all]: () => true, + [statusFilters.open]: ({ status, translation, lastInteraction, submissionTimeout }) => + Task.isOpen(status, translation, lastInteraction, submissionTimeout), + [statusFilters.inProgress]: ({ status, translation, lastInteraction, submissionTimeout }) => + Task.isInProgress(status, translation, lastInteraction, submissionTimeout), + [statusFilters.inReview]: ({ status }) => status === taskStatus.AwaitingReview, + [statusFilters.inDispute]: ({ status }) => status === taskStatus.DisputeCreated, + [statusFilters.finished]: ({ status, translation, lastInteraction, submissionTimeout }) => + Task.isCompleted(status, translation, lastInteraction, submissionTimeout), + [statusFilters.incomplete]: ({ status, translation, lastInteraction, submissionTimeout }) => + Task.isIncomplete(status, translation, lastInteraction, submissionTimeout), +}; From 3dc251efd300099b3ee38f4ad41b1a1426fc5658 Mon Sep 17 00:00:00 2001 From: gratestas Date: Tue, 10 Jan 2023 11:37:31 +0300 Subject: [PATCH 037/132] feat(utils-task): add new functions to check task status --- src/utils/task/index.js | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/utils/task/index.js b/src/utils/task/index.js index cebd7b28..da9a0e2f 100644 --- a/src/utils/task/index.js +++ b/src/utils/task/index.js @@ -8,13 +8,17 @@ const isIncomplete = (status, translation, lastInteraction, submissionTimeout) = return translation === ''; } - if ([taskStatus.Created, taskStatus.Assigned].includes(status)) { + if (isPending(status)) { return moment(lastInteraction).add(submissionTimeout).isBefore(moment()); } return false; }; +const isInProgress = (status, translation, lastInteraction, submissionTimeout) => { + return status === taskStatus.Assigned && !Task.isIncomplete(status, translation, lastInteraction, submissionTimeout); +}; + const isFinalized = (status, translation, lastInteraction, submissionTimeout) => { return status === taskStatus.Resolved || isIncomplete(status, translation, lastInteraction, submissionTimeout); }; @@ -23,6 +27,14 @@ const isPending = status => { return [taskStatus.Created, taskStatus.Assigned].includes(status); }; +const isCompleted = (status, translation, lastInteraction, submissionTimeout) => { + return status === taskStatus.Resolved && !isIncomplete(status, translation, lastInteraction, submissionTimeout); +}; + +const isOpen = (status, translation, lastInteraction, submissionTimeout) => { + return status === taskStatus.Created && !isIncomplete(status, translation, lastInteraction, submissionTimeout); +}; + const getCurrentPrice = (requesterDeposit, minPrice, maxPrice, lastInteraction, submissionTimeout, status) => { if (requesterDeposit) return requesterDeposit; @@ -93,8 +105,11 @@ const getCurrentParty = (account, requester, translator, challenger) => { }; const Task = { + isCompleted, isIncomplete, + isInProgress, isFinalized, + isOpen, isPending, getCurrentParty, getCurrentPrice, From f5822b178bb4a60e4f8e7899ee48660dbb0f94f7 Mon Sep 17 00:00:00 2001 From: gratestas Date: Tue, 10 Jan 2023 11:38:50 +0300 Subject: [PATCH 038/132] refactor: change number of tasks to fetch --- src/hooks/queries/useTasksQuery.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/queries/useTasksQuery.js b/src/hooks/queries/useTasksQuery.js index 6b4bf5d2..1ce230b0 100644 --- a/src/hooks/queries/useTasksQuery.js +++ b/src/hooks/queries/useTasksQuery.js @@ -3,7 +3,7 @@ import { gql } from 'graphql-request'; const tasksQuery = gql` query TasksPage($skip: Int) { - tasks(first: 30, skip: $skip, orderBy: taskID, orderDirection: desc) { + tasks(first: 100, skip: $skip, orderBy: taskID, orderDirection: desc) { id taskID challenger From ee7ec1f5bb639b83c1744b5052d78527d8ad85c5 Mon Sep 17 00:00:00 2001 From: gratestas Date: Tue, 10 Jan 2023 11:42:37 +0300 Subject: [PATCH 039/132] refactor: use new implementation of task filters --- src/pages/TranslatorDashboard/TaskListFetcher.jsx | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/pages/TranslatorDashboard/TaskListFetcher.jsx b/src/pages/TranslatorDashboard/TaskListFetcher.jsx index 185b4eab..c8de2b42 100644 --- a/src/pages/TranslatorDashboard/TaskListFetcher.jsx +++ b/src/pages/TranslatorDashboard/TaskListFetcher.jsx @@ -4,23 +4,30 @@ import styled from 'styled-components'; import { Spin } from '~/adapters/antd'; import * as r from '~/app/routes'; import TaskList from '~/features/translator/TaskList'; -import { statusFilters, useFilters } from '~/features/translator'; import DismissableAlert from '~/features/ui/DismissableAlert'; import TopLoadingBar from '~/shared/TopLoadingBar'; + import { useTasksQuery } from '~/hooks/queries/useTasksQuery'; +import { useWeb3 } from '~/hooks/useWeb3'; +import { useTasksFilter } from '~/context/TasksFilterProvider'; +import { statusFilters } from '~/consts/statusFilters'; +import { getTasksByFilters } from '~/utils/getTasksByFilters'; export default function TaskListFetcher() { - const [filters] = useFilters(); + const { account } = useWeb3(); + const { filters } = useTasksFilter(); const { tasks, isLoading } = useTasksQuery(0); + const filteredTasks = !isLoading ? getTasksByFilters(tasks, account, filters) : []; + console.log({ filteredTasks }); const showFootnote = [statusFilters.open].includes(filters.status) && tasks !== undefined; return ( <> - {filterDescriptionMap[getFilterTreeName({ status, allTasks: filters.allTasks })]} - {tasks && } + {filterDescriptionMap[getFilterTreeName({ status: filters.status, allTasks: filters.allTasks })]} + {filteredTasks && } ); From a2ebbb98649bfa6bef23a52241682eae2fd13a29 Mon Sep 17 00:00:00 2001 From: gratestas Date: Tue, 10 Jan 2023 11:44:38 +0300 Subject: [PATCH 040/132] chore: change default value of statusFilter --- src/context/TasksFilterProvider.jsx | 27 +++++---------------------- 1 file changed, 5 insertions(+), 22 deletions(-) diff --git a/src/context/TasksFilterProvider.jsx b/src/context/TasksFilterProvider.jsx index a145e505..3be8f88d 100644 --- a/src/context/TasksFilterProvider.jsx +++ b/src/context/TasksFilterProvider.jsx @@ -2,17 +2,14 @@ import React, { useState, createContext, useContext, useEffect } from 'react'; import { useHistory, useLocation } from 'react-router'; import PropTypes from 'prop-types'; -const TasksFilterContext = createContext({ - statusFilter: 'all', - allTasksFilter: true, -}); +const TasksFilterContext = createContext(); export const TasksFilterProvider = ({ children }) => { const location = useLocation(); const history = useHistory(); const queryParams = new URLSearchParams(location.search); - const [statusFilter, setStatusFilter] = useState(queryParams.get('status') || ''); + const [statusFilter, setStatusFilter] = useState(queryParams.get('status') || 'all'); const [allTasksFilter, setAllTasksFilter] = useState(queryParams.get('allTasks') === 'true'); useEffect(() => { @@ -28,12 +25,12 @@ export const TasksFilterProvider = ({ children }) => { allTasks: allTasksFilter, }; - const setFilters = newFilters => { - setStatusFilter(newFilters.status || ''); + const updateFilters = newFilters => { + setStatusFilter(newFilters.status || 'all'); setAllTasksFilter(newFilters.allTasks || false); }; - return {children}; + return {children}; }; export const useTasksFilter = () => { @@ -47,17 +44,3 @@ export const useTasksFilter = () => { TasksFilterProvider.propTypes = { children: PropTypes.node.isRequired, }; - -export const statusFilters = { - all: 'all', - open: 'open', - inProgress: 'inProgress', - inReview: 'inReview', - inDispute: 'inDispute', - finished: 'finished', - incomplete: 'incomplete', -}; - -export const getStatusFilter = filterName => { - return statusFilters[filterName] ?? statusFilters.all; -}; From 7400c3d0fa14a09fb5604f3465ca8a677129a137 Mon Sep 17 00:00:00 2001 From: gratestas Date: Wed, 11 Jan 2023 00:22:37 +0300 Subject: [PATCH 041/132] refactor: create useLocalStorrage hook --- src/hooks/useLocalStorage.js | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 src/hooks/useLocalStorage.js diff --git a/src/hooks/useLocalStorage.js b/src/hooks/useLocalStorage.js new file mode 100644 index 00000000..ce936af3 --- /dev/null +++ b/src/hooks/useLocalStorage.js @@ -0,0 +1,24 @@ +import { useState } from 'react'; + +const useLocalStorage = (key, defaultValue) => { + const [storedValue, setStoredValue] = useState(() => { + try { + const value = localStorage.getItem(key); + return value ? JSON.parse(value) : defaultValue; + } catch (err) { + return defaultValue; + } + }); + + const setStoreValue = newValue => { + try { + localStorage.setItem(key, JSON.stringify(newValue)); + } finally { + setStoredValue(newValue); + } + }; + + return [storedValue, setStoreValue]; +}; + +export default useLocalStorage; From f272cb95f571cb8c9eb0916b455a60bdc2c53dec Mon Sep 17 00:00:00 2001 From: gratestas Date: Wed, 11 Jan 2023 00:25:01 +0300 Subject: [PATCH 042/132] refactor: create context provider to store translator's skills --- src/context/TranslatorSkillsProvider.jsx | 90 ++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 src/context/TranslatorSkillsProvider.jsx diff --git a/src/context/TranslatorSkillsProvider.jsx b/src/context/TranslatorSkillsProvider.jsx new file mode 100644 index 00000000..0ce1c32a --- /dev/null +++ b/src/context/TranslatorSkillsProvider.jsx @@ -0,0 +1,90 @@ +import React, { createContext, useContext, useEffect, useState } from 'react'; +import PropTypes from 'prop-types'; + +import { indexBy, map, prop } from '~/shared/fp'; +import allLanguages from '~/assets/fixtures/languages'; +import useLocalStorage from '~/hooks/useLocalStorage'; + +const allLanguageCodes = allLanguages.map(({ code }) => code); +const STORAGE_KEY = 'persist-state/translatorSkills'; + +export const EMPTY_SKILL = { + language: undefined, + level: undefined, +}; + +const initialState = { + ids: [], + entities: {}, +}; + +const TranslatorSkillsContext = createContext({ + state: initialState, + actions: { + updateSkills: () => {}, + clearSkills: () => {}, + }, + selectors: { + selectAllSkillLanguages: () => [], + selectAllSkills: () => [], + }, +}); + +const TranslatorSkillsProvider = ({ children }) => { + const [state, setState] = useState(initialState); + const [storedValue, setStoreValue] = useLocalStorage(STORAGE_KEY, initialState); + + useEffect(() => { + if (!isEmpty(storedValue)) { + setState(storedValue); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useEffect(() => { + setStoreValue(state); + }, [setStoreValue, state]); + + const updateSkills = skills => { + const getLanguage = prop('language'); + const newEntities = indexBy(getLanguage, skills); + const newIds = map(getLanguage, skills); + setState({ ...state, entities: newEntities, ids: newIds }); + }; + + const clearSkills = () => { + setState(initialState); + }; + + const selectAllSkillLanguages = state => state.ids.filter(code => allLanguageCodes.includes(code)) ?? null; + + const selectAllSkills = state => { + const allSkills = selectAllSkillLanguages(state).map(language => state.entities[language]); + return allSkills.length > 0 ? allSkills : [EMPTY_SKILL]; + }; + + const actions = { updateSkills, clearSkills }; + const selectors = { selectAllSkillLanguages, selectAllSkills }; + + return ( + + {children} + + ); +}; + +export default TranslatorSkillsProvider; + +export const useTranslatorSkills = () => { + const context = useContext(TranslatorSkillsContext); + if (context === undefined) { + throw new Error('useTranslatorSkills must be used within a TranslatorSkillsProvider'); + } + return context; +}; + +TranslatorSkillsProvider.propTypes = { + children: PropTypes.node.isRequired, +}; + +const isEmpty = obj => Object.entries(obj).every(([_, value]) => !value); From a2944fdbec7992df8eafec4cde655d2e09c602a7 Mon Sep 17 00:00:00 2001 From: gratestas Date: Wed, 11 Jan 2023 00:35:57 +0300 Subject: [PATCH 043/132] refactor: implement TranslatorSkillsProvider --- src/context/TasksFilterProvider.jsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/context/TasksFilterProvider.jsx b/src/context/TasksFilterProvider.jsx index 3be8f88d..ff134fd5 100644 --- a/src/context/TasksFilterProvider.jsx +++ b/src/context/TasksFilterProvider.jsx @@ -1,6 +1,7 @@ import React, { useState, createContext, useContext, useEffect } from 'react'; import { useHistory, useLocation } from 'react-router'; import PropTypes from 'prop-types'; +import TranslatorSkillsProvider from './TranslatorSkillsProvider'; const TasksFilterContext = createContext(); @@ -30,7 +31,11 @@ export const TasksFilterProvider = ({ children }) => { setAllTasksFilter(newFilters.allTasks || false); }; - return {children}; + return ( + + {children} + + ); }; export const useTasksFilter = () => { From dafabfeb4c949d01be76ef6b43e472d7d4dea16a Mon Sep 17 00:00:00 2001 From: gratestas Date: Wed, 11 Jan 2023 00:40:10 +0300 Subject: [PATCH 044/132] chore: remove console log --- src/pages/TranslatorDashboard/TaskListHeader.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/TranslatorDashboard/TaskListHeader.jsx b/src/pages/TranslatorDashboard/TaskListHeader.jsx index ac2ffa18..778a7d84 100644 --- a/src/pages/TranslatorDashboard/TaskListHeader.jsx +++ b/src/pages/TranslatorDashboard/TaskListHeader.jsx @@ -42,7 +42,6 @@ function TaskOwnershipFilterContainer() { updateFilters, } = useTasksFilter(); - console.log({ updateFilters }); const handleFilterChange = React.useCallback( value => { updateFilters({ status, allTasks: value }); From aad95b99a757357c3c6eaea662842ab918f42a22 Mon Sep 17 00:00:00 2001 From: gratestas Date: Wed, 11 Jan 2023 00:42:37 +0300 Subject: [PATCH 045/132] chore: remove console log --- src/pages/TranslatorDashboard/TaskListFetcher.jsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pages/TranslatorDashboard/TaskListFetcher.jsx b/src/pages/TranslatorDashboard/TaskListFetcher.jsx index c8de2b42..3f8a14b2 100644 --- a/src/pages/TranslatorDashboard/TaskListFetcher.jsx +++ b/src/pages/TranslatorDashboard/TaskListFetcher.jsx @@ -3,6 +3,7 @@ import { Link } from 'react-router-dom'; import styled from 'styled-components'; import { Spin } from '~/adapters/antd'; import * as r from '~/app/routes'; + import TaskList from '~/features/translator/TaskList'; import DismissableAlert from '~/features/ui/DismissableAlert'; import TopLoadingBar from '~/shared/TopLoadingBar'; @@ -20,8 +21,8 @@ export default function TaskListFetcher() { const { tasks, isLoading } = useTasksQuery(0); const filteredTasks = !isLoading ? getTasksByFilters(tasks, account, filters) : []; - console.log({ filteredTasks }); const showFootnote = [statusFilters.open].includes(filters.status) && tasks !== undefined; + return ( <> From 076f17b94720680c3c388980f68c67f74e56cd71 Mon Sep 17 00:00:00 2001 From: gratestas Date: Wed, 11 Jan 2023 00:44:42 +0300 Subject: [PATCH 046/132] refactor: implement new implementation of translator skills --- .../translator/TranslatorSettingsForm.jsx | 53 ++++++++----------- 1 file changed, 21 insertions(+), 32 deletions(-) diff --git a/src/features/translator/TranslatorSettingsForm.jsx b/src/features/translator/TranslatorSettingsForm.jsx index 059fa0b3..d8e27b70 100644 --- a/src/features/translator/TranslatorSettingsForm.jsx +++ b/src/features/translator/TranslatorSettingsForm.jsx @@ -1,34 +1,36 @@ import React from 'react'; import t from 'prop-types'; -import { useDispatch, useSelector } from 'react-redux'; import produce from 'immer'; import { Link } from 'react-router-dom'; import styled from 'styled-components'; + import { Col, Form, Row } from 'antd'; import { Alert } from '~/adapters/antd'; import * as r from '~/app/routes'; import allLanguages from '~/assets/fixtures/languages'; + import Button from '~/shared/Button'; import { AddIcon, RemoveIcon } from '~/shared/icons'; import { LanguageSelect, LevelSelect } from '~/shared/LanguageSelect'; import AffixContainer from '~/shared/AffixContainer'; import Spacer from '~/shared/Spacer'; -import { cancelSaveSkills, saveSkills, selectAllSkills } from './translatorSlice'; + +import { useTranslatorSkills, EMPTY_SKILL } from '~/context/TranslatorSkillsProvider'; const emptyLevels = []; export default function TranslatorSettingsForm() { + const { state, actions, selectors } = useTranslatorSkills(); + const { updateSkills, clearSkills } = actions; + const { selectAllSkills } = selectors; + const [form] = Form.useForm(); - const storedSkills = useSelector(selectAllSkills); - const [state, setState] = React.useState({ skills: ensureAtLeastOneEmptySkill(storedSkills) }); + const [formState, setFormState] = React.useState({ skills: selectAllSkills(state) }); - const handleValuesChange = React.useCallback( - (_, allValues) => { - setState(allValues); - }, - [setState] - ); + const handleValuesChange = React.useCallback((_, allValues) => { + setFormState(allValues); + }, []); const resetLevelOnLanguageChange = React.useCallback( change => { @@ -44,23 +46,21 @@ export default function TranslatorSettingsForm() { [form] ); - const dispatch = useDispatch(); - const handleReturnClick = React.useCallback(() => { - dispatch(cancelSaveSkills()); - }, [dispatch]); + clearSkills(); + }, [clearSkills]); const handleFinish = React.useCallback( - values => { - dispatch(saveSkills(values)); + ({ skills }) => { + updateSkills(skills); }, - [dispatch] + [updateSkills] ); - const totalLanguagesReached = allLanguages.length === state.skills.length; + const totalLanguagesReached = allLanguages.length === formState.skills.length; return ( -
+ {(fields, { add, remove }) => { return ( @@ -70,8 +70,8 @@ export default function TranslatorSettingsForm() { @@ -169,17 +169,6 @@ const StyledJumboButton = styled(Button)` border-radius: 0.75rem; `; -const EMPTY_SKILL = { - language: undefined, - level: undefined, -}; - -const ensureAtLeastOneEmptySkill = produce(skills => { - if (skills.length === 0) { - skills.push(EMPTY_SKILL); - } -}); - const levelsByLanguage = allLanguages.reduce( (acc, { code, levels }) => Object.assign(acc, { From 646f3f7d7295575ef463e36b6acd45547ba7f5e3 Mon Sep 17 00:00:00 2001 From: gratestas Date: Wed, 11 Jan 2023 00:48:50 +0300 Subject: [PATCH 047/132] refactor: create withRequiredSkills to redirect user if no skills stored --- .../TranslatorDashboard/withRequiredSkills.js | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 src/pages/TranslatorDashboard/withRequiredSkills.js diff --git a/src/pages/TranslatorDashboard/withRequiredSkills.js b/src/pages/TranslatorDashboard/withRequiredSkills.js new file mode 100644 index 00000000..992d2553 --- /dev/null +++ b/src/pages/TranslatorDashboard/withRequiredSkills.js @@ -0,0 +1,28 @@ +import React, { useEffect } from 'react'; +import { useHistory } from 'react-router'; +import * as r from '~/app/routes'; + +import { useTranslatorSkills } from '~/context/TranslatorSkillsProvider'; + +export const withRequiredSkills = WrappedComponent => { + const WithRequiredSkills = props => { + const history = useHistory(); + const { state } = useTranslatorSkills(); + + useEffect(() => { + if (!state.ids.length) { + history.push({ + pathname: r.TRANSLATOR_SETTINGS, + state: { message: 'Please set your skills first.' }, + }); + } + }, [state, history]); + + return ; + }; + WithRequiredSkills.displayName = `withAuthCheck(${ + WrappedComponent.displayName || WrappedComponent.name || 'Component' + })`; + + return WithRequiredSkills; +}; From 1d2320be9c8da8959df9a09e7258de5f9d2e390b Mon Sep 17 00:00:00 2001 From: gratestas Date: Wed, 11 Jan 2023 00:52:31 +0300 Subject: [PATCH 048/132] refactor: wrap TranslatoDashboard with createed HOC --- src/pages/TranslatorDashboard/TranslatorDashboard.jsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/pages/TranslatorDashboard/TranslatorDashboard.jsx b/src/pages/TranslatorDashboard/TranslatorDashboard.jsx index a554b7e7..f1ce9c2b 100644 --- a/src/pages/TranslatorDashboard/TranslatorDashboard.jsx +++ b/src/pages/TranslatorDashboard/TranslatorDashboard.jsx @@ -6,8 +6,9 @@ import AffixContainer from '~/shared/AffixContainer'; import MultiCardLayout from '../layouts/MultiCardLayout'; import TaskListHeader from './TaskListHeader'; import TaskListFetcher from './TaskListFetcher'; +import { withRequiredSkills } from './withRequiredSkills'; -export default function TranslatorDashboard() { +const WrappedTranslatorDashboard = () => { return ( `Translator Dashboard | ${title}`}> @@ -39,7 +40,10 @@ export default function TranslatorDashboard() { ); -} +}; + +const TranslatorDashboard = withRequiredSkills(WrappedTranslatorDashboard); +export default TranslatorDashboard; const StyledDivider = styled(Divider)` border-top-color: ${props => props.theme.color.primary.default}; From 62931e365b27347d4cf97a450607474fcd27e3be Mon Sep 17 00:00:00 2001 From: gratestas Date: Wed, 11 Jan 2023 01:11:14 +0300 Subject: [PATCH 049/132] feat: add redirection to TranslatorDashboard on skill save --- src/features/translator/TranslatorSettingsForm.jsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/features/translator/TranslatorSettingsForm.jsx b/src/features/translator/TranslatorSettingsForm.jsx index d8e27b70..33def3c3 100644 --- a/src/features/translator/TranslatorSettingsForm.jsx +++ b/src/features/translator/TranslatorSettingsForm.jsx @@ -1,7 +1,7 @@ import React from 'react'; import t from 'prop-types'; import produce from 'immer'; -import { Link } from 'react-router-dom'; +import { Link, useHistory } from 'react-router-dom'; import styled from 'styled-components'; import { Col, Form, Row } from 'antd'; @@ -20,6 +20,7 @@ import { useTranslatorSkills, EMPTY_SKILL } from '~/context/TranslatorSkillsProv const emptyLevels = []; export default function TranslatorSettingsForm() { + const history = useHistory(); const { state, actions, selectors } = useTranslatorSkills(); const { updateSkills, clearSkills } = actions; const { selectAllSkills } = selectors; @@ -53,8 +54,9 @@ export default function TranslatorSettingsForm() { const handleFinish = React.useCallback( ({ skills }) => { updateSkills(skills); + history.push(r.TRANSLATOR_DASHBOARD); }, - [updateSkills] + [history, updateSkills] ); const totalLanguagesReached = allLanguages.length === formState.skills.length; From a80dc97675ce1147b12f9e9dcbeb10eb91ddd2d7 Mon Sep 17 00:00:00 2001 From: gratestas Date: Wed, 11 Jan 2023 01:18:51 +0300 Subject: [PATCH 050/132] fix: fix skills match displaying bug in TaskCard component --- src/features/translator/TaskList.jsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/features/translator/TaskList.jsx b/src/features/translator/TaskList.jsx index 87367c92..35310693 100644 --- a/src/features/translator/TaskList.jsx +++ b/src/features/translator/TaskList.jsx @@ -1,14 +1,15 @@ import React from 'react'; import t from 'prop-types'; import styled from 'styled-components'; + import { Col, Row, Tooltip, Typography } from 'antd'; import Spacer from '~/shared/Spacer'; import TaskCard from '~/features/tasks/TaskCard'; -import { useShallowEqualSelector } from '~/adapters/react-redux'; import { TaskCardFooterInfoDisplay } from '../tasks/TaskCardFooter'; -import { selectAllSkills } from './translatorSlice'; + import { useIPFSQuery } from '~/hooks/queries/useIPFSQuery'; import taskStatus from '~/consts/taskStatus'; +import { useTranslatorSkills } from '~/context/TranslatorSkillsProvider'; export default function TaskList({ data, showFootnote }) { return data.length === 0 ? ( @@ -68,7 +69,10 @@ function TranslatorTaskCard(props) { const { data } = useIPFSQuery(props.metaEvidence.URI); const metadata = data?.metadata; const minimumLevel = minimumLevelByQuality[metadata?.expectedQuality]; - const skills = useShallowEqualSelector(selectAllSkills); + + const { state, selectors } = useTranslatorSkills(); + const { selectAllSkills } = selectors; + const skills = selectAllSkills(state); const hasSkill = React.useMemo(() => { const hasSourceLanguageSkill = skills.some( From c70c60c314410479c8a002242147ff23e416b18d Mon Sep 17 00:00:00 2001 From: gratestas Date: Wed, 11 Jan 2023 01:28:10 +0300 Subject: [PATCH 051/132] fix: enable content blocker if no skills match --- .../byStatus/Created/CreatedForOther.jsx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/pages/TranslationDetails/TaskStatusDetails/byStatus/Created/CreatedForOther.jsx b/src/pages/TranslationDetails/TaskStatusDetails/byStatus/Created/CreatedForOther.jsx index 8d561a11..c6251c73 100644 --- a/src/pages/TranslationDetails/TaskStatusDetails/byStatus/Created/CreatedForOther.jsx +++ b/src/pages/TranslationDetails/TaskStatusDetails/byStatus/Created/CreatedForOther.jsx @@ -3,9 +3,6 @@ import styled from 'styled-components'; import { Link } from 'react-router-dom'; import * as r from '~/app/routes'; -import { useShallowEqualSelector } from '~/adapters/react-redux'; -import { selectAllSkills } from '~/features/translator/translatorSlice'; - import Button from '~/shared/Button'; import ContentBlocker from '~/shared/ContentBlocker'; import Spacer from '~/shared/Spacer'; @@ -19,14 +16,18 @@ import TaskAssignmentDepositFetcher from '../../components/TaskAssignmentDeposit import { useWeb3 } from '~/hooks/useWeb3'; import { useParamsCustom } from '~/hooks/useParamsCustom'; import { useTask } from '~/hooks/useTask'; +import { useTranslatorSkills } from '~/context/TranslatorSkillsProvider'; export default function CreatedForOther() { const { chainId } = useWeb3(); const { id } = useParamsCustom(chainId); const { task } = useTask(id); + const { state, selectors } = useTranslatorSkills(); + const { selectAllSkills } = selectors; + const skills = selectAllSkills(state); + const minimumLevel = minimumLevelByQuality[task.expectedQuality]; - const skills = useShallowEqualSelector(selectAllSkills); const hasSkill = React.useMemo(() => { const hasSourceLanguageSkill = skills.some( @@ -79,7 +80,7 @@ export default function CreatedForOther() { } >
- +
From 0917587b42336001e0811ee4bde0146def9fdc23 Mon Sep 17 00:00:00 2001 From: gratestas Date: Wed, 11 Jan 2023 13:45:23 +0300 Subject: [PATCH 052/132] fix(task-details): show connection alert msg when wallet disconnected --- src/features/web3/RequiredWalletGateway.jsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/features/web3/RequiredWalletGateway.jsx b/src/features/web3/RequiredWalletGateway.jsx index a53b351f..aca30d6c 100644 --- a/src/features/web3/RequiredWalletGateway.jsx +++ b/src/features/web3/RequiredWalletGateway.jsx @@ -5,18 +5,19 @@ import { Alert } from '~/adapters/antd'; import WalletConnectionModal from './WalletConnectionModal'; import RequiredWeb3Gateway from './RequiredWeb3Gateway'; import { useWeb3 } from '~/hooks/useWeb3'; +import { injected } from './connectors'; function RequiredWalletGateway({ message, children, missing, error, render, renderMissing, renderError }) { - const { account } = useWeb3(); - const isConnecting = false; - const isConnected = true; + const { account, active, chainId, connector } = useWeb3(); + + const isConnected = active && chainId && connector.name === injected.name; const hasAccount = isConnected && !!account; const missingWalletWarning = ; return ( - {hasAccount ? children || render({ account }) : isConnecting ? null : missingWalletWarning} + {hasAccount ? children || render({ account }) : missingWalletWarning} ); } From 2ec9e33775c108b4fb235955c70337d3a379b15d Mon Sep 17 00:00:00 2001 From: gratestas Date: Wed, 11 Jan 2023 13:49:49 +0300 Subject: [PATCH 053/132] fix: show url search parameters only on translator dashboard --- src/app/MainRouter.jsx | 10 ++--- src/context/TasksFilterProvider.jsx | 15 +++---- .../TranslatorDashboard.jsx | 45 ++++++++++--------- 3 files changed, 34 insertions(+), 36 deletions(-) diff --git a/src/app/MainRouter.jsx b/src/app/MainRouter.jsx index 2cd3508f..8f37e539 100644 --- a/src/app/MainRouter.jsx +++ b/src/app/MainRouter.jsx @@ -19,7 +19,7 @@ import { history } from '~/store'; import Content from './Content'; import * as r from './routes'; import Web3ConnectionManager from '~/components/Web3ConnectionManager'; -import { TasksFilterProvider } from '~/context/TasksFilterProvider'; +import TranslatorSkillsProvider from '~/context/TranslatorSkillsProvider'; const fallback = ; @@ -53,8 +53,8 @@ export default function MainRouter() { >
- - + + @@ -81,8 +81,8 @@ export default function MainRouter() { - - + +