diff --git a/.env b/.env new file mode 100644 index 0000000..87059ec --- /dev/null +++ b/.env @@ -0,0 +1,2 @@ +REACT_APP_SEARCH_INPUT_TEXT_DELAYER=300 +REACT_APP_SEARCH_INPUT_TEXT_MINIMUM_LENGTH=3 diff --git a/.npmrc b/.npmrc index 71000d1..c79a090 100644 --- a/.npmrc +++ b/.npmrc @@ -1,3 +1,3 @@ @rbl:registry=https://us-central1-npm.pkg.dev/rbl-common/rbl/ -//us-central1-npm.pkg.dev/rbl-common/rbl/:_authToken="ya29.A0ARrdaM9VpQcc5egcSN7zzEGQLyvz5jZiXEkIDmnsV2RW3KBbhbq8qkRHMUcC6gxknE9LuDW3mt4Dz3teWYXfI-4WGr6_mTQqj60BhAg4sPA7wov7PM-E3QonNwTN9De41ARPJUyvfc8Mi2GVoYzle3MJ_8KNYo4" +//us-central1-npm.pkg.dev/rbl-common/rbl/:_authToken="ya29.A0ARrdaM-GfFYg4Qg9a29AOiKlQFyYdH5R94i7UZSHz0WNS16b8pe2hFyogr2i25gwXXqfwx9zY9HRG0cO3ydMaPrz-lAXIwmuIEKJBJ26Rgg92mt6FScoaLLH6479ywWMgkgojWgVcaSJy9CxmMG48SpxVCB01Q" //us-central1-npm.pkg.dev/rbl-common/rbl/:always-auth=true \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index af088e4..19f9ea7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20512,12 +20512,14 @@ "acorn-import-assertions": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", - "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==" + "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", + "requires": {} }, "acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==" + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "requires": {} }, "acorn-node": { "version": "1.8.2", @@ -20617,7 +20619,8 @@ "ajv-keywords": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==" + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "requires": {} }, "ansi-escapes": { "version": "4.3.2", @@ -20881,7 +20884,8 @@ "babel-plugin-named-asset-import": { "version": "0.3.8", "resolved": "https://registry.npmjs.org/babel-plugin-named-asset-import/-/babel-plugin-named-asset-import-0.3.8.tgz", - "integrity": "sha512-WXiAc++qo7XcJ1ZnTYGtLxmBCVbddAml3CEXgWaBzNzLNoxtQ8AiGEFDMOhot9XjTCQbvP5E77Fj9Gk924f00Q==" + "integrity": "sha512-WXiAc++qo7XcJ1ZnTYGtLxmBCVbddAml3CEXgWaBzNzLNoxtQ8AiGEFDMOhot9XjTCQbvP5E77Fj9Gk924f00Q==", + "requires": {} }, "babel-plugin-polyfill-corejs2": { "version": "0.3.1", @@ -21850,7 +21854,8 @@ "css-prefers-color-scheme": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-6.0.3.tgz", - "integrity": "sha512-4BqMbZksRkJQx2zAjrokiGMd07RqOa2IxIrrN10lyBe9xhn9DEvjUK79J6jkeiv9D9hQFXKb6g1jwU62jziJZA==" + "integrity": "sha512-4BqMbZksRkJQx2zAjrokiGMd07RqOa2IxIrrN10lyBe9xhn9DEvjUK79J6jkeiv9D9hQFXKb6g1jwU62jziJZA==", + "requires": {} }, "css-select": { "version": "4.2.1", @@ -21962,7 +21967,8 @@ "cssnano-utils": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-3.1.0.tgz", - "integrity": "sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==" + "integrity": "sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==", + "requires": {} }, "csso": { "version": "4.2.0", @@ -22868,7 +22874,8 @@ "eslint-plugin-react-hooks": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.3.0.tgz", - "integrity": "sha512-XslZy0LnMn+84NEG9jSGR6eGqaZB3133L8xewQo3fQagbQuGt7a63gf+P1NGKZavEYEC3UXaWEAA/AqDkuN6xA==" + "integrity": "sha512-XslZy0LnMn+84NEG9jSGR6eGqaZB3133L8xewQo3fQagbQuGt7a63gf+P1NGKZavEYEC3UXaWEAA/AqDkuN6xA==", + "requires": {} }, "eslint-plugin-testing-library": { "version": "5.1.0", @@ -23884,7 +23891,8 @@ "icss-utils": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", - "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==" + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "requires": {} }, "idb": { "version": "6.1.5", @@ -24691,7 +24699,8 @@ "jest-pnp-resolver": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", - "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==" + "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", + "requires": {} }, "jest-regex-util": { "version": "27.5.1", @@ -26071,7 +26080,8 @@ "postcss-browser-comments": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/postcss-browser-comments/-/postcss-browser-comments-4.0.0.tgz", - "integrity": "sha512-X9X9/WN3KIvY9+hNERUqX9gncsgBA25XaeR+jshHz2j8+sYyHktHw1JdKuMjeLpGktXidqDhA7b/qm1mrBDmgg==" + "integrity": "sha512-X9X9/WN3KIvY9+hNERUqX9gncsgBA25XaeR+jshHz2j8+sYyHktHw1JdKuMjeLpGktXidqDhA7b/qm1mrBDmgg==", + "requires": {} }, "postcss-calc": { "version": "8.2.4", @@ -26136,7 +26146,8 @@ "postcss-custom-media": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-8.0.0.tgz", - "integrity": "sha512-FvO2GzMUaTN0t1fBULDeIvxr5IvbDXcIatt6pnJghc736nqNgsGao5NT+5+WVLAQiTt6Cb3YUms0jiPaXhL//g==" + "integrity": "sha512-FvO2GzMUaTN0t1fBULDeIvxr5IvbDXcIatt6pnJghc736nqNgsGao5NT+5+WVLAQiTt6Cb3YUms0jiPaXhL//g==", + "requires": {} }, "postcss-custom-properties": { "version": "12.1.5", @@ -26165,22 +26176,26 @@ "postcss-discard-comments": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.1.1.tgz", - "integrity": "sha512-5JscyFmvkUxz/5/+TB3QTTT9Gi9jHkcn8dcmmuN68JQcv3aQg4y88yEHHhwFB52l/NkaJ43O0dbksGMAo49nfQ==" + "integrity": "sha512-5JscyFmvkUxz/5/+TB3QTTT9Gi9jHkcn8dcmmuN68JQcv3aQg4y88yEHHhwFB52l/NkaJ43O0dbksGMAo49nfQ==", + "requires": {} }, "postcss-discard-duplicates": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz", - "integrity": "sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==" + "integrity": "sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==", + "requires": {} }, "postcss-discard-empty": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz", - "integrity": "sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==" + "integrity": "sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==", + "requires": {} }, "postcss-discard-overridden": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz", - "integrity": "sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==" + "integrity": "sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==", + "requires": {} }, "postcss-double-position-gradients": { "version": "3.1.1", @@ -26202,7 +26217,8 @@ "postcss-flexbugs-fixes": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/postcss-flexbugs-fixes/-/postcss-flexbugs-fixes-5.0.2.tgz", - "integrity": "sha512-18f9voByak7bTktR2QgDveglpn9DTbBWPUzSOe9g0N4WR/2eSt6Vrcbf0hmspvMI6YWGywz6B9f7jzpFNJJgnQ==" + "integrity": "sha512-18f9voByak7bTktR2QgDveglpn9DTbBWPUzSOe9g0N4WR/2eSt6Vrcbf0hmspvMI6YWGywz6B9f7jzpFNJJgnQ==", + "requires": {} }, "postcss-focus-visible": { "version": "6.0.4", @@ -26223,12 +26239,14 @@ "postcss-font-variant": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz", - "integrity": "sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==" + "integrity": "sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==", + "requires": {} }, "postcss-gap-properties": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-3.0.3.tgz", - "integrity": "sha512-rPPZRLPmEKgLk/KlXMqRaNkYTUpE7YC+bOIQFN5xcu1Vp11Y4faIXv6/Jpft6FMnl6YRxZqDZG0qQOW80stzxQ==" + "integrity": "sha512-rPPZRLPmEKgLk/KlXMqRaNkYTUpE7YC+bOIQFN5xcu1Vp11Y4faIXv6/Jpft6FMnl6YRxZqDZG0qQOW80stzxQ==", + "requires": {} }, "postcss-image-set-function": { "version": "4.0.6", @@ -26241,7 +26259,8 @@ "postcss-initial": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/postcss-initial/-/postcss-initial-4.0.1.tgz", - "integrity": "sha512-0ueD7rPqX8Pn1xJIjay0AZeIuDoF+V+VvMt/uOnn+4ezUKhZM/NokDeP6DwMNyIoYByuN/94IQnt5FEkaN59xQ==" + "integrity": "sha512-0ueD7rPqX8Pn1xJIjay0AZeIuDoF+V+VvMt/uOnn+4ezUKhZM/NokDeP6DwMNyIoYByuN/94IQnt5FEkaN59xQ==", + "requires": {} }, "postcss-js": { "version": "4.0.0", @@ -26282,12 +26301,14 @@ "postcss-logical": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-5.0.4.tgz", - "integrity": "sha512-RHXxplCeLh9VjinvMrZONq7im4wjWGlRJAqmAVLXyZaXwfDWP73/oq4NdIp+OZwhQUMj0zjqDfM5Fj7qby+B4g==" + "integrity": "sha512-RHXxplCeLh9VjinvMrZONq7im4wjWGlRJAqmAVLXyZaXwfDWP73/oq4NdIp+OZwhQUMj0zjqDfM5Fj7qby+B4g==", + "requires": {} }, "postcss-media-minmax": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/postcss-media-minmax/-/postcss-media-minmax-5.0.0.tgz", - "integrity": "sha512-yDUvFf9QdFZTuCUg0g0uNSHVlJ5X1lSzDZjPSFaiCWvjgsvu8vEVxtahPrLMinIDEEGnx6cBe6iqdx5YWz08wQ==" + "integrity": "sha512-yDUvFf9QdFZTuCUg0g0uNSHVlJ5X1lSzDZjPSFaiCWvjgsvu8vEVxtahPrLMinIDEEGnx6cBe6iqdx5YWz08wQ==", + "requires": {} }, "postcss-merge-longhand": { "version": "5.1.3", @@ -26348,7 +26369,8 @@ "postcss-modules-extract-imports": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", - "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==" + "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", + "requires": {} }, "postcss-modules-local-by-default": { "version": "4.0.0", @@ -26405,7 +26427,8 @@ "postcss-normalize-charset": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz", - "integrity": "sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==" + "integrity": "sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==", + "requires": {} }, "postcss-normalize-display-values": { "version": "5.1.0", @@ -26490,12 +26513,14 @@ "postcss-overflow-shorthand": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-3.0.3.tgz", - "integrity": "sha512-CxZwoWup9KXzQeeIxtgOciQ00tDtnylYIlJBBODqkgS/PU2jISuWOL/mYLHmZb9ZhZiCaNKsCRiLp22dZUtNsg==" + "integrity": "sha512-CxZwoWup9KXzQeeIxtgOciQ00tDtnylYIlJBBODqkgS/PU2jISuWOL/mYLHmZb9ZhZiCaNKsCRiLp22dZUtNsg==", + "requires": {} }, "postcss-page-break": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-3.0.4.tgz", - "integrity": "sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==" + "integrity": "sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==", + "requires": {} }, "postcss-place": { "version": "7.0.4", @@ -26583,7 +26608,8 @@ "postcss-replace-overflow-wrap": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz", - "integrity": "sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==" + "integrity": "sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==", + "requires": {} }, "postcss-selector-not": { "version": "5.0.0", @@ -27158,7 +27184,8 @@ "redux-thunk": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.1.tgz", - "integrity": "sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q==" + "integrity": "sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q==", + "requires": {} }, "regenerate": { "version": "1.4.2", @@ -28100,7 +28127,8 @@ "style-loader": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.1.tgz", - "integrity": "sha512-GPcQ+LDJbrcxHORTRes6Jy2sfvK2kS6hpSfI/fXhPt+spVzxF6LJ1dHLN9zIGmVaaP044YKaIatFaufENRiDoQ==" + "integrity": "sha512-GPcQ+LDJbrcxHORTRes6Jy2sfvK2kS6hpSfI/fXhPt+spVzxF6LJ1dHLN9zIGmVaaP044YKaIatFaufENRiDoQ==", + "requires": {} }, "styled-components": { "version": "5.3.5", @@ -28827,7 +28855,8 @@ "use-isomorphic-layout-effect": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.1.tgz", - "integrity": "sha512-L7Evj8FGcwo/wpbv/qvSfrkHFtOpCzvM5yl2KVyDJoylVuSvzphiiasmjgQPttIGBAy2WKiBNR98q8w7PiNgKQ==" + "integrity": "sha512-L7Evj8FGcwo/wpbv/qvSfrkHFtOpCzvM5yl2KVyDJoylVuSvzphiiasmjgQPttIGBAy2WKiBNR98q8w7PiNgKQ==", + "requires": {} }, "use-latest": { "version": "1.2.0", @@ -29147,7 +29176,8 @@ "ws": { "version": "8.5.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz", - "integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==" + "integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==", + "requires": {} } } }, @@ -29549,7 +29579,8 @@ "ws": { "version": "7.4.6", "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", - "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==" + "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", + "requires": {} }, "xml-name-validator": { "version": "3.0.0", diff --git a/src/redux/tokenSearchSlice.js b/src/redux/tokenSearchSlice.js index cf2c399..30c12c9 100644 --- a/src/redux/tokenSearchSlice.js +++ b/src/redux/tokenSearchSlice.js @@ -1,7 +1,7 @@ import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; import retry from 'async-retry'; import { stringify } from 'flatted'; -import {searchTokensAsync} from "../tokenSearch/helpers/async"; +import { searchTokensAsync } from "../tokenSearch/helpers/async"; export const setPair = createAsyncThunk( 'token/setPair', @@ -29,19 +29,20 @@ const setPairSearchTimestamp = createAsyncThunk( export const searchTokenPairs = createAsyncThunk( 'token/search', async (searchString, thunkAPI) => { - console.log("searchTokenPairs") + console.log("searchTokenPairs: " + searchString) try { // const { strategy } = thunkAPI.getState().velox; const pairSearchTimestamp = new Date().getTime(); thunkAPI.dispatch(setPairSearchTimestamp(pairSearchTimestamp)); const data = await retry( - () => searchTokensAsync(searchString, JSON.parse(`{"identifiers":{"blockchain":"Avalanche","chainId":"43114","exchange":"Pangolin"},"key":"pangolin","tableSuffix":"pangolin"}`)),//todo this should be props into the component or something -- from src/containers/exchangeSelector/allowableExchanges.ts in velox + //todo this should be props into the component or something -- from src/containers/exchangeSelector/allowableExchanges.ts in velox + () => searchTokensAsync(searchString, JSON.parse(`{"identifiers":{"blockchain":"Avalanche","chainId":"43114","exchange":"Pangolin"},"key":"pangolin","tableSuffix":"pangolin"}`)), { retries: 1 } ); - console.log("data",data) + console.log("data", data) return { data, pairSearchTimestamp }; } catch (e) { - console.log("err searchTokenPairs",e) + console.log("err searchTokenPairs", e) throw new Error(stringify(e, Object.getOwnPropertyNames(e))); } } @@ -116,6 +117,5 @@ export const tokenSearchSlice = createSlice({ }, }); -export const { setSearchText, startSelecting, stopSelecting, toggleSelecting } = - tokenSearchSlice.actions; +export const { setSearchText, startSelecting, stopSelecting, toggleSelecting } = tokenSearchSlice.actions; export default tokenSearchSlice.reducer; diff --git a/src/tokenSearch/SearchFiltering.jsx b/src/tokenSearch/SearchFiltering.jsx index 6e077ed..a4cf591 100644 --- a/src/tokenSearch/SearchFiltering.jsx +++ b/src/tokenSearch/SearchFiltering.jsx @@ -1,52 +1,81 @@ -import React, { useState } from "react" +import React from "react"; +import { + SearchSelectionNetwork, + SearchSelectionExchange +} from "./SearchFilteringSelections"; -const NetworkSelection = ({networkName, setNetwork, network}) =>{ - - return <> - setNetwork(networkName)}/> - {networkName} - -} - -export const SearchFiltering = () => { - const [network, setNetwork ] = useState()//todo link into redux state +/** + * searchNetworks: networks to be searched + * setSearchNetworks: function to set "searchNetworks" to the state. + * searchExchanges: exchanges to be searched + * setSearchExchanges: function to set "setSearchExchanges" to the state. + * @returns + */ +export const SearchFiltering = ({ searchNetworks, setSearchNetworks, searchExchanges, setSearchExchanges }) => { + // ---------------------------------------------------------------------------------------------------------------------------------------------------- + // Rendering. return ( -
-
Select blockchain
- - - - -
- - {network==="Ethereum" && <> - - All exchanges - - UniswapV3 - - SushiSwap - } - - {network==="Avalanche" && <> - - All exchanges - - Pangolin - - TraderJoe - } - - {network==="BSC" && <> - - All exchanges - - PancakeSwap - - MDex - } +
+ + + +
+ + + + + +
) } -export default SearchFiltering \ No newline at end of file +export default SearchFiltering; \ No newline at end of file diff --git a/src/tokenSearch/SearchFilteringSelections.jsx b/src/tokenSearch/SearchFilteringSelections.jsx new file mode 100644 index 0000000..0088de2 --- /dev/null +++ b/src/tokenSearch/SearchFilteringSelections.jsx @@ -0,0 +1,98 @@ +// Lists all the exchanges and their allowed networks. +const networksExchanged = { + Ethereum: ['UniswapV3', 'SushiSwap'], + Avalanche: ['Pangolin', 'TraderJoe'], + BSC: ['PancakeSwap', 'MDex'] +}; + +// Lists all the exchanges and their allowed networks. +const exchangesNetworked = { + UniswapV3: ['Ethereum'], + SushiSwap: ['Ethereum'], + Pangolin: ['Avalanche'], + TraderJoe: ['Avalanche'], + PancakeSwap: ['BSC'], + MDex: ['BSC'] +}; + +/** + * + * + * @param {*} param0 + * @returns + */ +export const SearchSelectionNetwork = ({ networkName, searchNetworks, setSearchNetworks, searchExchanges, setSearchExchanges }) => { + // ---------------------------------------------------------------------------------------------------------------------------------------------------- + // Rendering. + return <> + { + // Validate that "searchNetworks" contains "networkName". + if (searchNetworks.includes(networkName)) { + // Removes "networkName" from "searchNetworks". + searchNetworks = searchNetworks.filter(network => network !== networkName); + + // Removes the exchanges associated to "networkName" since it's removed from the search. + searchExchanges = searchExchanges.filter(exchance => !networksExchanged[networkName].includes(exchance)); + } + else { + // Adds "networkName" into "searchNetworks". + searchNetworks.push(networkName); + } + + // Sets the new value for "searchNetworks" using a "Set" to *lazy* clean duplicates; who knows? + // It may not be the most efficient client wise operation, but considering it has no concurrent request, who cares. + // The stability gains are worth it in the long run if we decide to add functionality that add risk to the uniqueness + // of the data contained in the array. + setSearchNetworks([...new Set(searchNetworks)]); + + // Sets the new value for "searchExchanges", using the same laziness for duplicate cleaning. + setSearchExchanges([...new Set(searchExchanges)]); + }} + /> + {networkName} + +}; + +/** + * + * @param {*} param0 + * @returns + */ +export const SearchSelectionExchange = ({ exchangeName, searchExchanges, setSearchExchanges, searchNetworks }) => { + // Validates that the any of the selected networks are present in "exchangeNetworks". + if (searchNetworks.some(network => exchangesNetworked[exchangeName].includes(network))) { + // -------------------------------------------------------------------------------------------------------------------------------------------------- + // Rendering. + return <> + { + let filter; + + + // Creates a new set from the array of "searchNetworks". + // It may not be the most efficient client wise operation, but considering it has no concurrent request, who cares. + // The stability gains are worth it in the long run if we decide to add functionality that add risk to the uniqueness + // of the data contained in the array. + filter = new Set(searchExchanges); + + // Ensures that "filter" has "networkName" then proceeds to deletion or adition of the network name according to it's presence in the set. + filter[filter.has(exchangeName) ? 'delete' : 'add'](exchangeName); + + // Sets the new value for "searchNetworks". + setSearchExchanges([...filter]); + }} + /> + {exchangeName} + + } + + + // ---------------------------------------------------------------------------------------------------------------------------------------------------- + // Rendering. + return <>; +}; \ No newline at end of file diff --git a/src/tokenSearch/SearchInput.jsx b/src/tokenSearch/SearchInput.jsx index 3b5f81d..e5587a0 100644 --- a/src/tokenSearch/SearchInput.jsx +++ b/src/tokenSearch/SearchInput.jsx @@ -1,16 +1,14 @@ -import React from 'react'; +import React, { + useState +} from 'react'; import { useDispatch, useSelector } from 'react-redux'; import styled from 'styled-components'; - import magnifyingGlass from './icon-search.svg'; import { searchTokenPairs, - setSearchText, -} from '../redux/tokenSearchSlice'; -import { startSelecting, stopSelecting, - toggleSelecting, + toggleSelecting } from '../redux/tokenSearchSlice'; const PairField = styled.div` @@ -65,59 +63,30 @@ const combinePairText = (pair) => { return ''; }; -const SearchInput = () => { +const SearchInput = ({ searchText, setSearchText }) => { const dispatch = useDispatch(); - const searchText = useSelector( - (state) => state?.searchText - ); - const isSelecting = useSelector( - (state) => state?.isSelecting - ); - const isLoading = useSelector((state) => state.isLoading); - const fetchError = useSelector( - (state) => state?.fetchError - ); - const selectedPair = useSelector( - (state) => state?.selectedPair - ); - const selectedPairText = selectedPair && combinePairText(selectedPair); + // const searchText = useSelector((state) => state?.searchText); + // const isSelecting = useSelector((state) => state?.isSelecting); + // const isLoading = useSelector((state) => state.isLoading); + // const selectedPair = useSelector((state) => state?.selectedPair); + // const selectedPairText = selectedPair && combinePairText(selectedPair); - const onChange = (e) => { - const newInputText = e.target.value; - dispatch(searchTokenPairs(newInputText)); - dispatch(setSearchText(newInputText)); - }; - const onClick = () => dispatch(startSelecting()); - const onKeyDown = (e) => e.code === 'Escape' && dispatch(stopSelecting()); - //todo throw to a global error boundary - if (fetchError) { - return ( - - {}} - /> - - ); - } + // Initialise the "searchDelayer" state. + // The search delayer is used to prevent the search to be launched on each keystrokes and rather waits for a short moment defined in the env file. + const [searchDelayer, setSearchDelayer] = useState(); - let value; - if (isSelecting) { - value = searchText; - } else { - value = selectedPairText || 'Select a token pair..'; - } + // ---------------------------------------------------------------------------------------------------------------------------------------------------- + // Rendering. return ( - + dispatch(startSelecting())}> setSearchText_onChange(searchDelayer, setSearchDelayer, e.target.value, setSearchText)} + onKeyDown={e => e.code === 'Escape' && dispatch(stopSelecting())} /> { ); }; export default SearchInput; + +/** + * Function that handle the delayer of the search; this is done to avoid flooding of the server through GraphQL queries by the user, intentionnally or not. + * This is done client side, it is worth as much as the dev console. + * Something server side might should be added to cover that aspect. + * + * @param {*} searchDelayer + * @param {*} setSearchDelayer + * @param {*} searchText + * @param {*} setSearchText + */ +const setSearchText_onChange = (searchDelayer, setSearchDelayer, searchText, setSearchText) => { + // Clears the previous search delayer. + clearTimeout(searchDelayer); + + // Sets the new search delayer and the new searc text value. + setSearchDelayer(setTimeout(value => setSearchText(value), process.env.REACT_APP_SEARCH_INPUT_TEXT_DELAYER, searchText)); +}; \ No newline at end of file diff --git a/src/tokenSearch/index.jsx b/src/tokenSearch/index.jsx index 6c4b5a5..79961d7 100644 --- a/src/tokenSearch/index.jsx +++ b/src/tokenSearch/index.jsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { searchTokenPairs, @@ -7,32 +7,81 @@ import { import SearchInput from "./SearchInput"; import SearchResult from "./SearchResult"; import SearchFiltering from "./SearchFiltering"; + + export const TokenSearch = () => { const dispatch = useDispatch(); - const {isSelecting, searchText, isLoading} = useSelector( - (state) => state - ); const searchRef = useRef(); + const { isSelecting, isLoading } = useSelector((state) => state); + const fetchError = useSelector((state) => state?.fetchError); + + + // Initialise the state variables and their setters. + const [searchText, setSearchText] = useState(''); + const [searchNetworks, setSearchNetworks] = useState([]); + const [searchExchanges, setSearchExchanges] = useState([]); + - useEffect(() => { - window.onclick = (e) => { - if (!searchRef?.current?.contains(e.target)) { - dispatch(stopSelecting()); - } - }; - //todo return remove onclick - //todo useOnClickOutside might be a better implementation? - }, [dispatch]); + // Handles the closing of the modal box of the search. + useEffect(() => searchModal_closing(dispatch, searchRef), [dispatch]); + // Keep an eye on the value of "searchText" to fire fire the grahQL query if any value change is detected. + useEffect(() => searchText_useEffect(dispatch, searchText, searchNetworks, searchExchanges), [dispatch, searchText, searchNetworks, searchExchanges]); + + // ---------------------------------------------------------------------------------------------------------------------------------------------------- + // Rendering. return (
- - {isSelecting && !searchText && - - } - {isSelecting && } + + {!fetchError && isSelecting && + } + {!fetchError && isSelecting && + }
); }; - export default TokenSearch; + +/** + * Function that handles the validation of the search parameters in order to dispatch the GraphQL query and capture the results. + * + * @param {*} dispatch + * @param {*} searchText + * @param {*} searchNetworks + * @param {*} searchExchanges + */ +const searchText_useEffect = (dispatch, searchText, searchNetworks, searchExchanges) => { + // Ensures that "searchText" exists and it's length is sufficient. + // Ensures that at least one network is selected. + // Ensures that at least one exchange is selected. + if (searchText && searchText.length >= process.env.REACT_APP_SEARCH_INPUT_TEXT_MINIMUM_LENGTH && searchNetworks.length > 0 && searchExchanges.length > 0) { + // Execute the async GraphQL query. + dispatch(searchTokenPairs(searchText)) + } +}; + +/** + * Function that handles the closing of the search modal when the user clicks outside of the said modal. + * + * @param {*} dispatch + * @param {*} searchRef + */ +const searchModal_closing = (dispatch, searchRef) => { + // We have to use "onmousedown" to avoid an issue where the user select the text in the search bar with a mouse click and releases + // the button while outside the search bar; which closes the search; which is not the intended/expected behaviour. + window.onmousedown = e => { + if (!searchRef?.current?.contains(e.target)) { + dispatch(stopSelecting()); + } + }; +}; \ No newline at end of file