diff --git a/cypress/component/unit/truncate.cy.tsx b/cypress/component/unit/truncate.cy.tsx index 976ec515..b8b2a7d8 100644 --- a/cypress/component/unit/truncate.cy.tsx +++ b/cypress/component/unit/truncate.cy.tsx @@ -1,6 +1,6 @@ /// -import { truncate } from '../../../src/util/truncate'; +import { truncate } from '../../../src/lib/util/truncate'; describe('Component | Unit | Util | Truncate', () => { before(() => { diff --git a/package-lock.json b/package-lock.json index 00df370c..44dd4be4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,16 +1,17 @@ { "name": "@massalabs/react-ui-kit", - "version": "0.0.3", + "version": "0.0.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@massalabs/react-ui-kit", - "version": "0.0.3", + "version": "0.0.4", "dependencies": { "@headlessui/react": "^1.7.15", "copy-to-clipboard": "^3.3.3", "currency.js": "^2.0.4", + "dot-object": "^2.1.5", "minidenticons": "^4.2.1", "react": "^18.2.0", "react-currency-input-field": "^3.6.11", @@ -20,7 +21,8 @@ "react-number-format": "^5.2.2", "tw-colors": "^1.2.5", "viem": "^1.5.2", - "vite-plugin-svgr": "^3.2.0" + "vite-plugin-svgr": "^3.2.0", + "zustand": "^4.5.2" }, "devDependencies": { "@babel/preset-env": "^7.21.4", @@ -40,6 +42,7 @@ "@testing-library/dom": "^9.2.0", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^14.0.0", + "@types/dot-object": "^2.1.6", "@types/jest": "^29.5.1", "@types/react": "^18.2.46", "@types/react-dom": "^18.0.11", @@ -68,6 +71,10 @@ "tailwindcss": "^3.3.1", "typescript": "^5.2.2", "vite": "^4.5.3" + }, + "peerDependencies": { + "@massalabs/massa-web3": "^4.0.2-dev", + "@massalabs/wallet-provider": "^2.0.1-dev" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -2821,6 +2828,12 @@ "react-dom": "^16 || ^17 || ^18" } }, + "node_modules/@hicaru/bearby.js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/@hicaru/bearby.js/-/bearby.js-0.5.8.tgz", + "integrity": "sha512-K6mLazzHkDNF5Qmx5iQ4+UqmvBJxtuwg1ZHEDEvOKB1SV/QNuKJ54/HRlLMqJE0RHam2zP7s++RlnMZUIfWgGg==", + "peer": true + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.11", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", @@ -3823,6 +3836,15 @@ "integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==", "dev": true }, + "node_modules/@lukeed/csprng": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@lukeed/csprng/-/csprng-1.1.0.tgz", + "integrity": "sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==", + "peer": true, + "engines": { + "node": ">=8" + } + }, "node_modules/@massalabs/eslint-config": { "version": "0.0.9", "resolved": "https://registry.npmjs.org/@massalabs/eslint-config/-/eslint-config-0.0.9.tgz", @@ -3839,6 +3861,95 @@ "eslint-plugin-tsdoc": "^0.2.17" } }, + "node_modules/@massalabs/massa-web3": { + "version": "4.0.2-dev.20240410152026", + "resolved": "https://registry.npmjs.org/@massalabs/massa-web3/-/massa-web3-4.0.2-dev.20240410152026.tgz", + "integrity": "sha512-SGsXbxICvdwOnRThh2MSswvzA/k5OfrlJSujZBgNA2CoATXGBar11YcDEIgVttHRRqQSJG39EQcMOs3eRC0irA==", + "peer": true, + "dependencies": { + "@massalabs/wallet-provider": "^2.0.0", + "@massalabs/web3-utils": "^1.4.9", + "@noble/ed25519": "^1.7.3", + "@noble/hashes": "^1.2.0", + "@types/ws": "^8.5.4", + "@web3pack/base58-check": "^1.0.3", + "axios": "^0.26.1", + "bignumber.js": "^9.1.1", + "bip39": "^3.0.4", + "bs58check": "^3.0.1", + "buffer": "^6.0.3", + "crypto-js": "^4.1.1", + "dotenv": "^16.0.3", + "js-base64": "^3.7.5", + "string_decoder": "^1.3.0", + "tslib": "^2.5.2", + "util": "^0.12.5", + "varint": "^6.0.0" + }, + "optionalDependencies": { + "bufferutil": "^4.0.7", + "utf-8-validate": "^6.0.2" + } + }, + "node_modules/@massalabs/massa-web3/node_modules/@massalabs/wallet-provider": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@massalabs/wallet-provider/-/wallet-provider-2.0.0.tgz", + "integrity": "sha512-3BKtYszSfKMOi85cxOB1q60uiMWYmdteV28stx2NjT42VtSyKOuWiTJq4KaWbf3ov+fkg+/HknXdl5bhDghbSQ==", + "peer": true, + "dependencies": { + "@hicaru/bearby.js": "^0.5.7", + "@massalabs/web3-utils": "^1.4.9-dev", + "axios": "^0.28.0", + "bs58check": "^3.0.1", + "buffer": "^6.0.3", + "uid": "^2.0.1" + }, + "optionalDependencies": { + "bufferutil": "^4.0.7", + "utf-8-validate": "^6.0.2" + } + }, + "node_modules/@massalabs/massa-web3/node_modules/@massalabs/wallet-provider/node_modules/axios": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.28.1.tgz", + "integrity": "sha512-iUcGA5a7p0mVb4Gm/sy+FSECNkPFT4y7wt6OM/CDpO/OnNCvSs3PoMG8ibrC9jRoGYU0gUK5pXVC4NPXq6lHRQ==", + "peer": true, + "dependencies": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/@massalabs/massa-web3/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "peer": true, + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/@massalabs/massa-web3/node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "peer": true + }, "node_modules/@massalabs/prettier-config-as": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/@massalabs/prettier-config-as/-/prettier-config-as-0.0.2.tgz", @@ -3848,6 +3959,101 @@ "assemblyscript-prettier": "^1.0.6" } }, + "node_modules/@massalabs/wallet-provider": { + "version": "2.0.1-dev.20240423112601", + "resolved": "https://registry.npmjs.org/@massalabs/wallet-provider/-/wallet-provider-2.0.1-dev.20240423112601.tgz", + "integrity": "sha512-2NvwKho1Couwd2/gXZ0ZGBymmTiuX6rxjoX2gQnEu8LI2i15uDfRba5Gv6NSSvgLcjExzbhrJOQzPRgDAJhDlQ==", + "peer": true, + "dependencies": { + "@hicaru/bearby.js": "^0.5.7", + "@massalabs/web3-utils": "^1.4.9-dev", + "axios": "^0.28.0", + "bs58check": "^3.0.1", + "buffer": "^6.0.3", + "uid": "^2.0.1" + }, + "optionalDependencies": { + "bufferutil": "^4.0.7", + "utf-8-validate": "^6.0.2" + } + }, + "node_modules/@massalabs/wallet-provider/node_modules/axios": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.28.1.tgz", + "integrity": "sha512-iUcGA5a7p0mVb4Gm/sy+FSECNkPFT4y7wt6OM/CDpO/OnNCvSs3PoMG8ibrC9jRoGYU0gUK5pXVC4NPXq6lHRQ==", + "peer": true, + "dependencies": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/@massalabs/wallet-provider/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "peer": true, + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/@massalabs/wallet-provider/node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "peer": true + }, + "node_modules/@massalabs/web3-utils": { + "version": "1.4.9", + "resolved": "https://registry.npmjs.org/@massalabs/web3-utils/-/web3-utils-1.4.9.tgz", + "integrity": "sha512-8G91gs6HqpPpR860QDUOzugr81WJTGkBgXQS1gq/oXqt16fPkw2xlsq4c2CFHDjRF9/ftE/JaybiNZ4ilcbEyg==", + "peer": true, + "dependencies": { + "bignumber.js": "^9.1.2", + "buffer": "^6.0.3", + "events": "^3.3.0", + "string_decoder": "^1.3.0" + } + }, + "node_modules/@massalabs/web3-utils/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "peer": true, + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, "node_modules/@mdx-js/react": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-2.3.0.tgz", @@ -3918,6 +4124,18 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/@noble/ed25519": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@noble/ed25519/-/ed25519-1.7.3.tgz", + "integrity": "sha512-iR8GBkDt0Q3GyaVcIu7mSsVIqnFbkbRzGLWlvhwunacoLwt4J3swfKhfaM6rN6WY+TBGoYT1GtT1mIh2/jGbRQ==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "peer": true + }, "node_modules/@noble/hashes": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", @@ -6851,6 +7069,12 @@ "integrity": "sha512-w5jZ0ee+HaPOaX25X2/2oGR/7rgAQSYII7X7pp0m9KgBfMP7uKfMfTvcpl5Dj+eDBbpxKGiqE+flqDr6XTd2RA==", "dev": true }, + "node_modules/@types/dot-object": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@types/dot-object/-/dot-object-2.1.6.tgz", + "integrity": "sha512-G1e4SNPOuO72ZXv7wz/W2x29CzQtpxko3G9hBiHqGg/AvFIKoArCs2nbc/WPXnnUkO+1dmvX9WQCyj5gIlAzZg==", + "dev": true + }, "node_modules/@types/ejs": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/@types/ejs/-/ejs-3.1.3.tgz", @@ -7096,7 +7320,7 @@ "version": "15.7.7", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.7.tgz", "integrity": "sha512-FbtmBWCcSa2J4zL781Zf1p5YUBXQomPEcep9QZCfRfQgTxz3pJWiDFLebohZ9fFntX5ibzOkSsrJ0TEew8cAog==", - "dev": true + "devOptional": true }, "node_modules/@types/qs": { "version": "6.9.8", @@ -7114,7 +7338,7 @@ "version": "18.2.46", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.46.tgz", "integrity": "sha512-nNCvVBcZlvX4NU1nRRNV/mFl1nNRuTuslAJglQsq+8ldXe5Xv0Wd2f7WTE3jOxhLH2BFfiZGC6GCp+kHQbgG+w==", - "dev": true, + "devOptional": true, "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -7143,7 +7367,7 @@ "version": "0.16.4", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.4.tgz", "integrity": "sha512-2L9ifAGl7wmXwP4v3pN4p2FLhD0O1qsJpvKmNin5VA8+UvNVb447UDaAEV6UdrkA+m/Xs58U1RFps44x6TFsVQ==", - "dev": true + "devOptional": true }, "node_modules/@types/semver": { "version": "7.5.3", @@ -7578,6 +7802,47 @@ "web-streams-polyfill": "^3.1.1" } }, + "node_modules/@web3pack/base-x": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@web3pack/base-x/-/base-x-1.0.2.tgz", + "integrity": "sha512-P3XgVnEQ1QFyUTmHzT1sXNprLyxE1aG8WAnk5/Fj+3j4AmsK4dRfMdV3t/aIdYP3i1KvjPRkQJhFXGpxUc+M8A==", + "peer": true + }, + "node_modules/@web3pack/base58-check": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@web3pack/base58-check/-/base58-check-1.0.3.tgz", + "integrity": "sha512-+s4HKOnJbIkj45jhGfSxgWkKsjBENuNbfqASPTLPy/yjGGv/jGCxzFCl0fb4gudV3x0gS50gLpJsC8qQ6cV1gw==", + "peer": true, + "dependencies": { + "@web3pack/base-x": "^1.0.1", + "buffer": "^6.0.3", + "hash.js": "^1.1.7" + } + }, + "node_modules/@web3pack/base58-check/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "peer": true, + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, "node_modules/@webassemblyjs/ast": { "version": "1.11.6", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", @@ -8300,8 +8565,7 @@ "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "node_modules/at-least-node": { "version": "1.0.0", @@ -8353,7 +8617,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -8376,6 +8639,15 @@ "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==", "dev": true }, + "node_modules/axios": { + "version": "0.26.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz", + "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==", + "peer": true, + "dependencies": { + "follow-redirects": "^1.14.8" + } + }, "node_modules/babel-core": { "version": "7.0.0-bridge.0", "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz", @@ -8572,11 +8844,16 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "node_modules/base-x": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-4.0.0.tgz", + "integrity": "sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw==", + "peer": true + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, "funding": [ { "type": "github", @@ -8631,6 +8908,15 @@ "node": "*" } }, + "node_modules/bignumber.js": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", + "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==", + "peer": true, + "engines": { + "node": "*" + } + }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -8650,6 +8936,15 @@ "wasm2js": "bin/wasm2js" } }, + "node_modules/bip39": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bip39/-/bip39-3.1.0.tgz", + "integrity": "sha512-c9kiwdk45Do5GL0vJMe7tS95VjCii65mYAH7DfWl3uW8AVzXKQVUm64i3hzVybBDMp9r7j9iNxR85+ul8MdN/A==", + "peer": true, + "dependencies": { + "@noble/hashes": "^1.2.0" + } + }, "node_modules/bl": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", @@ -8805,6 +9100,25 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/bs58": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-5.0.0.tgz", + "integrity": "sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ==", + "peer": true, + "dependencies": { + "base-x": "^4.0.0" + } + }, + "node_modules/bs58check": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-3.0.1.tgz", + "integrity": "sha512-hjuuJvoWEybo7Hn/0xOrczQKKEKD63WguEjlhLExYs2wUBcebDC1jDNK17eEAD2lYfw82d5ASC1d7K3SWszjaQ==", + "peer": true, + "dependencies": { + "@noble/hashes": "^1.2.0", + "bs58": "^5.0.0" + } + }, "node_modules/bser": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", @@ -8853,6 +9167,20 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "devOptional": true }, + "node_modules/bufferutil": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.8.tgz", + "integrity": "sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw==", + "hasInstallScript": true, + "optional": true, + "peer": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -8975,7 +9303,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, "dependencies": { "function-bind": "^1.1.1", "get-intrinsic": "^1.0.2" @@ -9367,7 +9694,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, "dependencies": { "delayed-stream": "~1.0.0" }, @@ -9733,6 +10059,12 @@ "node": ">= 8" } }, + "node_modules/crypto-js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==", + "peer": true + }, "node_modules/crypto-random-string": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", @@ -10308,7 +10640,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, "engines": { "node": ">=0.4.0" } @@ -10505,11 +10836,49 @@ "tslib": "^2.0.3" } }, + "node_modules/dot-object": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/dot-object/-/dot-object-2.1.5.tgz", + "integrity": "sha512-xHF8EP4XH/Ba9fvAF2LDd5O3IITVolerVV6xvkxoM8zlGEiCUrggpAnHyOoKJKCrhvPcGATFAUwIujj7bRG5UA==", + "dependencies": { + "commander": "^6.1.0", + "glob": "^7.1.6" + }, + "bin": { + "dot-object": "bin/dot-object" + } + }, + "node_modules/dot-object/node_modules/commander": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/dot-object/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/dotenv": { "version": "16.3.1", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", - "dev": true, "engines": { "node": ">=12" }, @@ -11357,7 +11726,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true, "peer": true, "engines": { "node": ">=0.8.x" @@ -11851,11 +12219,30 @@ "node": ">=0.4.0" } }, + "node_modules/follow-redirects": { + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "peer": true, + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "dev": true, "dependencies": { "is-callable": "^1.1.3" } @@ -11901,7 +12288,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dev": true, "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -12045,7 +12431,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", - "dev": true, "dependencies": { "function-bind": "^1.1.1", "has": "^1.0.3", @@ -12270,7 +12655,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dev": true, "dependencies": { "get-intrinsic": "^1.1.3" }, @@ -12372,7 +12756,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -12384,7 +12767,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -12396,7 +12778,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "dev": true, "dependencies": { "has-symbols": "^1.0.2" }, @@ -12407,6 +12788,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "peer": true, + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, "node_modules/hosted-git-info": { "version": "2.8.9", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", @@ -12558,7 +12949,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, "funding": [ { "type": "github", @@ -12794,7 +13184,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -12868,7 +13257,6 @@ "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -12965,7 +13353,6 @@ "version": "1.0.10", "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", - "dev": true, "dependencies": { "has-tostringtag": "^1.0.0" }, @@ -13185,7 +13572,6 @@ "version": "1.1.12", "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", - "dev": true, "dependencies": { "which-typed-array": "^1.1.11" }, @@ -15369,6 +15755,12 @@ "integrity": "sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==", "dev": true }, + "node_modules/js-base64": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.7.tgz", + "integrity": "sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==", + "peer": true + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -16691,7 +17083,6 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, "engines": { "node": ">= 0.6" } @@ -16700,7 +17091,6 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, "dependencies": { "mime-db": "1.52.0" }, @@ -16734,6 +17124,12 @@ "node": ">=15.14.0" } }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "peer": true + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -16990,6 +17386,18 @@ "webidl-conversions": "^3.0.0" } }, + "node_modules/node-gyp-build": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.1.tgz", + "integrity": "sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw==", + "optional": true, + "peer": true, + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -19068,7 +19476,6 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, "funding": [ { "type": "github", @@ -19638,7 +20045,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, "dependencies": { "safe-buffer": "~5.2.0" } @@ -20560,6 +20966,18 @@ "node": ">=0.8.0" } }, + "node_modules/uid": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/uid/-/uid-2.0.2.tgz", + "integrity": "sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==", + "peer": true, + "dependencies": { + "@lukeed/csprng": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/undici": { "version": "5.28.4", "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz", @@ -20818,11 +21236,32 @@ } } }, + "node_modules/use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/utf-8-validate": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-6.0.4.tgz", + "integrity": "sha512-xu9GQDeFp+eZ6LnCywXN/zBancWvOpUMzgjLPSjy4BRHSmTelvn2E0DG0o1sTiw5hkCKBHo8rwSKncfRfv2EEQ==", + "hasInstallScript": true, + "optional": true, + "peer": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, "node_modules/util": { "version": "0.12.5", "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", - "dev": true, "dependencies": { "inherits": "^2.0.3", "is-arguments": "^1.0.4", @@ -20888,6 +21327,12 @@ "spdx-expression-parse": "^3.0.0" } }, + "node_modules/varint": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz", + "integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==", + "peer": true + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -21347,7 +21792,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.11.tgz", "integrity": "sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==", - "dev": true, "dependencies": { "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.2", @@ -21621,6 +22065,33 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zustand": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.2.tgz", + "integrity": "sha512-2cN1tPkDVkwCy5ickKrI7vijSjPksFRfqS6237NzT0vqSsztTNnQdHw9mmN7uBdk3gceVXU0a+21jFzFzAc9+g==", + "dependencies": { + "use-sync-external-store": "1.2.0" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } } } } diff --git a/package.json b/package.json index e2ceab9f..93c8afac 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@massalabs/react-ui-kit", - "version": "0.0.3", + "version": "0.0.4", "type": "module", "files": ["src", "presets"], "main": "src/index.ts", @@ -28,6 +28,7 @@ "@headlessui/react": "^1.7.15", "copy-to-clipboard": "^3.3.3", "currency.js": "^2.0.4", + "dot-object": "^2.1.5", "minidenticons": "^4.2.1", "react": "^18.2.0", "react-currency-input-field": "^3.6.11", @@ -37,7 +38,12 @@ "react-number-format": "^5.2.2", "tw-colors": "^1.2.5", "viem": "^1.5.2", - "vite-plugin-svgr": "^3.2.0" + "vite-plugin-svgr": "^3.2.0", + "zustand": "^4.5.2" + }, + "peerDependencies": { + "@massalabs/massa-web3": "^4.0.2-dev", + "@massalabs/wallet-provider": "^2.0.1-dev" }, "devDependencies": { "@babel/preset-env": "^7.21.4", @@ -57,6 +63,7 @@ "@testing-library/dom": "^9.2.0", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^14.0.0", + "@types/dot-object": "^2.1.6", "@types/jest": "^29.5.1", "@types/react": "^18.2.46", "@types/react-dom": "^18.0.11", diff --git a/scripts/publish-dev.sh b/scripts/publish-dev.sh index 0f03eaa4..4eb90575 100755 --- a/scripts/publish-dev.sh +++ b/scripts/publish-dev.sh @@ -3,14 +3,16 @@ set -e NPM_PACKAGE=$(cat package.json | jq -r '.name') -npm version --preid dev --no-git-tag-version --no-commit-hooks prepatch +TAG=dev + +npm version --preid $TAG --no-git-tag-version --no-commit-hooks prepatch #Use timestamp as package suffix TIME=$(date -u +%Y%m%d%H%M%S) -sed -i "/version/s/dev.0/dev.$TIME/g" package.json +sed -i "/version/s/$TAG.0/$TAG.$TIME/g" package.json PUBLISH_VERSION=$(cat package.json | jq -r '.version') echo publishing $NPM_PACKAGE@$PUBLISH_VERSION # disable husky npm pkg delete scripts.prepare -npm publish --access public --tag dev +npm publish --access public --tag $TAG diff --git a/src/components/DollarValue/DollarValue.tsx b/src/components/DollarValue/DollarValue.tsx index a1b2a999..47cb66db 100644 --- a/src/components/DollarValue/DollarValue.tsx +++ b/src/components/DollarValue/DollarValue.tsx @@ -4,7 +4,7 @@ import React from 'react'; import { ComponentPropsWithoutRef } from 'react'; import { FiAlertTriangle } from 'react-icons/fi'; import { Tooltip } from '../Tooltip'; -import { formatFTAmount, parseAmount } from '../../util/parseAmount'; +import { formatFTAmount, parseAmount } from '../../lib/util/parseAmount'; export interface DollarValueProps extends ComponentPropsWithoutRef<'p'> { dollarValue?: string; @@ -17,7 +17,7 @@ export function DollarValue(props: DollarValueProps) { if (dollarValueError) { return ( -

-

+ ); } diff --git a/src/components/Dropdown/Dropdown.tsx b/src/components/Dropdown/Dropdown.tsx index a639adee..df387a0b 100644 --- a/src/components/Dropdown/Dropdown.tsx +++ b/src/components/Dropdown/Dropdown.tsx @@ -4,7 +4,7 @@ import React, { ReactNode, useEffect, useRef } from 'react'; import { ComponentPropsWithoutRef, useState, MouseEvent } from 'react'; import { FiChevronDown, FiChevronUp } from 'react-icons/fi'; -import useClickOutside from '../../util/useClickOutside'; +import useClickOutside from '../../lib/util/hooks/useClickOutside'; export interface IOption extends ComponentPropsWithoutRef<'div'> { icon?: ReactNode; diff --git a/src/components/Icons/Svg/Massa/StationLogo.tsx b/src/components/Icons/Svg/Massa/StationLogo.tsx index 62051629..ce452712 100644 --- a/src/components/Icons/Svg/Massa/StationLogo.tsx +++ b/src/components/Icons/Svg/Massa/StationLogo.tsx @@ -3,7 +3,7 @@ import React from 'react'; import { ComponentPropsWithoutRef } from 'react'; -import { Theme } from '../../../../util/types'; +import { Theme } from '../../../ThemeMode'; interface SVGProps extends ComponentPropsWithoutRef<'div'> { theme?: Theme | undefined; diff --git a/src/components/Plugin/Plugin.tsx b/src/components/Plugin/Plugin.tsx index 09ae90c8..bca322a1 100644 --- a/src/components/Plugin/Plugin.tsx +++ b/src/components/Plugin/Plugin.tsx @@ -4,7 +4,7 @@ import React, { useState } from 'react'; import { ReactNode, cloneElement } from 'react'; import { IconContext } from 'react-icons/lib'; -import { truncate } from '../../util/truncate'; +import { truncate } from '../../lib/util/truncate'; export interface PluginProps { preIcon: JSX.Element; diff --git a/src/components/RedirectTile/RedirectTile.tsx b/src/components/RedirectTile/RedirectTile.tsx index 0c8021e9..9cad37bf 100644 --- a/src/components/RedirectTile/RedirectTile.tsx +++ b/src/components/RedirectTile/RedirectTile.tsx @@ -3,7 +3,7 @@ import React from 'react'; import { ComponentPropsWithoutRef } from 'react'; -import { openInNewTab } from '../../util/utils'; +import { openInNewTab } from '../../lib/util/utils'; export interface RedirectTileProps extends ComponentPropsWithoutRef<'div'> { customClass?: string; diff --git a/src/components/Tag/Tag.tsx b/src/components/Tag/Tag.tsx index 42567cc6..c97aa22a 100644 --- a/src/components/Tag/Tag.tsx +++ b/src/components/Tag/Tag.tsx @@ -2,6 +2,13 @@ // @ts-ignore import React, { ComponentPropsWithoutRef } from 'react'; +export const tagTypes = { + success: 'success', + error: 'error', + warning: 'warning', + info: 'info', +}; + interface TagProps extends ComponentPropsWithoutRef<'div'> { type: string; children: React.ReactNode; diff --git a/src/components/ThemeMode/ThemeMode.tsx b/src/components/ThemeMode/ThemeMode.tsx index 28026178..45d1e76e 100644 --- a/src/components/ThemeMode/ThemeMode.tsx +++ b/src/components/ThemeMode/ThemeMode.tsx @@ -4,8 +4,9 @@ import React from 'react'; import { useState } from 'react'; import { FiMoon, FiSun } from 'react-icons/fi'; -import { useLocalStorage } from '../../util/useLocalStorage'; -import { Theme } from '../../util/types'; +import { useLocalStorage } from '../../lib/util/hooks/useLocalStorage'; + +export type Theme = 'theme-light' | 'theme-dark'; interface ThemeProps { onSetTheme?: (theme: Theme) => void; diff --git a/src/components/Token/Token.tsx b/src/components/Token/Token.tsx index f7d6e48f..c4e8d6cc 100644 --- a/src/components/Token/Token.tsx +++ b/src/components/Token/Token.tsx @@ -5,8 +5,8 @@ import { ComponentPropsWithoutRef } from 'react'; import { FiTrash2 } from 'react-icons/fi'; import { Button } from '../Button'; import { Tooltip } from '../Tooltip'; -import { formatFTAmount } from '../../util/parseAmount'; import { DollarValue } from '../DollarValue'; +import { formatFTAmount } from '../../lib/util/parseAmount'; export interface TokenProps extends ComponentPropsWithoutRef<'div'> { logo?: React.ReactNode; diff --git a/src/index.ts b/src/index.ts index e49f6f83..3c098200 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,3 @@ export * from './components'; import './global.css'; -export * from './util/types'; -export * from './util/parseAmount'; +export * from './lib/util/parseAmount'; diff --git a/src/lib/ConnectMassaWallets/components/BearbySvg.tsx b/src/lib/ConnectMassaWallets/components/BearbySvg.tsx new file mode 100644 index 00000000..3e3485e1 --- /dev/null +++ b/src/lib/ConnectMassaWallets/components/BearbySvg.tsx @@ -0,0 +1,39 @@ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore +import React from 'react'; + +/* eslint-disable max-len */ +export function BearbySvg() { + return ( + + + + + + + + + + + + ); +} diff --git a/src/lib/ConnectMassaWallets/components/BearbyWallet.tsx b/src/lib/ConnectMassaWallets/components/BearbyWallet.tsx new file mode 100644 index 00000000..8d60fc63 --- /dev/null +++ b/src/lib/ConnectMassaWallets/components/BearbyWallet.tsx @@ -0,0 +1,33 @@ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore +import React from 'react'; + +import { ConnectedAccount } from './ConnectedAccount'; +import { MASBalance } from './MASBalance'; +import { WalletError } from './WalletError'; +import Intl from '../i18n'; +import { useAccountStore } from '../store'; +import { BEARBY_INSTALL } from '../../massa-react/const'; + +export default function BearbyWallet() { + const { connectedAccount } = useAccountStore(); + + if (connectedAccount) { + return ( +
+ + +
+ ); + } + + return ( + + ); +} diff --git a/src/lib/ConnectMassaWallets/components/ConnectMassaWallet.stories.tsx b/src/lib/ConnectMassaWallets/components/ConnectMassaWallet.stories.tsx new file mode 100644 index 00000000..0b4a456d --- /dev/null +++ b/src/lib/ConnectMassaWallets/components/ConnectMassaWallet.stories.tsx @@ -0,0 +1,14 @@ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore +import React from 'react'; + +import { ConnectMassaWallet } from './ConnectMassaWallet'; + +export default { + title: 'Lib/ConnectMassaWallet', + component: ConnectMassaWallet, +}; + +export const _ConnectMassaWallet = { + render: () => , +}; diff --git a/src/lib/ConnectMassaWallets/components/ConnectMassaWallet.tsx b/src/lib/ConnectMassaWallets/components/ConnectMassaWallet.tsx new file mode 100644 index 00000000..40313166 --- /dev/null +++ b/src/lib/ConnectMassaWallets/components/ConnectMassaWallet.tsx @@ -0,0 +1,114 @@ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore +import React from 'react'; + +import { useEffect, useState } from 'react'; + +import { BearbySvg } from './BearbySvg'; +import BearbyWallet from './BearbyWallet'; +import SelectMassaWallet from './SelectMassaWallet'; +import StationWallet from './StationWallet'; +import { ChainStatus } from './Status/ChainStatus'; +import SwitchWalletButton from './SwitchWalletButton'; +import Intl from '../i18n'; +import { useAccountStore } from '../store'; +import { MassaWallet, Tooltip } from '../../../components'; +import { SUPPORTED_MASSA_WALLETS } from '../../massa-react/const'; + +export const ConnectMassaWallet = () => { + const { currentProvider, providers, setCurrentProvider, isFetching } = + useAccountStore(); + + const [selectedProvider, setSelectedProvider] = useState< + SUPPORTED_MASSA_WALLETS | undefined + >(currentProvider?.name() as SUPPORTED_MASSA_WALLETS); + + useEffect(() => { + const provider = providers.find((p) => p.name() === selectedProvider); + if (provider && !currentProvider) { + setCurrentProvider(provider); + } + }, [providers, selectedProvider, currentProvider, setCurrentProvider]); + + function renderWallet() { + switch (selectedProvider) { + case SUPPORTED_MASSA_WALLETS.MASSASTATION: + return ; + case SUPPORTED_MASSA_WALLETS.BEARBY: + return ; + default: + // Should not happen + return <>Error: no wallet selected; + } + } + + function renderSelectedWallet() { + switch (selectedProvider) { + case SUPPORTED_MASSA_WALLETS.MASSASTATION: + return ( + <> + + {Intl.t(`connect-wallet.${SUPPORTED_MASSA_WALLETS.MASSASTATION}`)} + + ); + case SUPPORTED_MASSA_WALLETS.BEARBY: + return ( + <> + + {Intl.t(`connect-wallet.${SUPPORTED_MASSA_WALLETS.BEARBY}`)} + + ); + } + } + + const noWalletSelected = !selectedProvider || isFetching; + + function renderNoWalletSelected() { + return ( + { + setSelectedProvider(providerName); + const provider = providers.find((p) => p.name() === providerName); + if (provider) { + setCurrentProvider(provider); + } + }} + /> + ); + } + + return ( +
+ {noWalletSelected ? ( + renderNoWalletSelected() + ) : ( + <> +
+
+ {renderSelectedWallet()} + + {selectedProvider === SUPPORTED_MASSA_WALLETS.BEARBY && ( + + )} +
+ { + setSelectedProvider(undefined); + setCurrentProvider(); + }} + /> +
+ {renderWallet()} + + )} +
+ ); +}; diff --git a/src/lib/ConnectMassaWallets/components/ConnectedAccount.tsx b/src/lib/ConnectMassaWallets/components/ConnectedAccount.tsx new file mode 100644 index 00000000..dd00a2db --- /dev/null +++ b/src/lib/ConnectMassaWallets/components/ConnectedAccount.tsx @@ -0,0 +1,29 @@ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore +import React from 'react'; + +import { Clipboard } from '../../../components'; +import { maskAddress } from '../../massa-react/utils'; +import { useAccountStore } from '../store'; + +interface ConnectedAccountProps { + maskLength?: number; +} + +export function ConnectedAccount(props: ConnectedAccountProps) { + const { maskLength } = props; + const { connectedAccount } = useAccountStore(); + + return ( +
+ +
+ ); +} diff --git a/src/lib/ConnectMassaWallets/components/MASBalance.tsx b/src/lib/ConnectMassaWallets/components/MASBalance.tsx new file mode 100644 index 00000000..ecb08224 --- /dev/null +++ b/src/lib/ConnectMassaWallets/components/MASBalance.tsx @@ -0,0 +1,46 @@ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore +import React from 'react'; + +import { useEffect, useState } from 'react'; + +import { fromMAS } from '@massalabs/massa-web3'; +import { IAccountBalanceResponse } from '@massalabs/wallet-provider'; + +import Intl from '../i18n'; +import { useAccountStore } from '../store'; +import { FetchingLine } from '../../../components'; +import { fetchMASBalance } from '../../massa-react/utils'; +import { massaToken } from '../../massa-react/const'; +import { formatAmount } from '../../util/parseAmount'; + +export function MASBalance() { + const [balance, setBalance] = useState(); + + const { connectedAccount } = useAccountStore(); + + useEffect(() => { + if (!connectedAccount) return; + fetchMASBalance(connectedAccount).then((balance) => { + setBalance(balance); + }); + }, [connectedAccount, setBalance]); + + const formattedBalance = formatAmount( + fromMAS(balance?.candidateBalance || '0').toString(), + 9, + ).amountFormattedFull; + + return ( +
+ {Intl.t('connect-wallet.connected-cards.wallet-balance')} + {balance === undefined ? ( + + ) : ( + <> + {formattedBalance} {massaToken} + + )} +
+ ); +} diff --git a/src/lib/ConnectMassaWallets/components/OperationToast.tsx b/src/lib/ConnectMassaWallets/components/OperationToast.tsx new file mode 100644 index 00000000..5a927d38 --- /dev/null +++ b/src/lib/ConnectMassaWallets/components/OperationToast.tsx @@ -0,0 +1,34 @@ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore +import React from 'react'; + +import Intl from '../i18n'; +import { generateExplorerLink } from '../../massa-react/utils'; + +type OperationToastProps = { + title: string; + operationId?: string; + isMainnet?: boolean; +}; + +export function OperationToast({ + title, + operationId, + isMainnet, +}: OperationToastProps) { + return ( +
+

{title}

+ {operationId && ( + + {Intl.t('toast.explorer')} + + )} +
+ ); +} diff --git a/src/lib/ConnectMassaWallets/components/SelectMassaWallet.tsx b/src/lib/ConnectMassaWallets/components/SelectMassaWallet.tsx new file mode 100644 index 00000000..dfa59f0b --- /dev/null +++ b/src/lib/ConnectMassaWallets/components/SelectMassaWallet.tsx @@ -0,0 +1,55 @@ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore +import React from 'react'; + +import { BearbySvg } from './BearbySvg'; +import { Disconnected } from './Status/Disconnected'; +import Intl from '../i18n'; +import { Dropdown, MassaWallet } from '../../../components'; +import { SUPPORTED_MASSA_WALLETS } from '../../massa-react/const'; + +const walletList = [ + { + name: SUPPORTED_MASSA_WALLETS.MASSASTATION, + icon: , + }, + { + name: SUPPORTED_MASSA_WALLETS.BEARBY, + icon: , + }, +]; + +interface SelectMassaWalletProps { + onClick: (providerName: SUPPORTED_MASSA_WALLETS) => void; +} + +const SelectMassaWallet = ({ onClick }: SelectMassaWalletProps) => { + const walletOptions = () => { + return walletList.map((provider) => ({ + item: Intl.t(`connect-wallet.${provider.name}`), + icon: provider.icon, + onClick: () => onClick(provider.name), + })); + }; + + return ( + <> +
+

+ {Intl.t('connect-wallet.card-destination.to')} +

+ +
+
+ +
+ + ); +}; + +export default SelectMassaWallet; diff --git a/src/lib/ConnectMassaWallets/components/StationSelectAccount.tsx b/src/lib/ConnectMassaWallets/components/StationSelectAccount.tsx new file mode 100644 index 00000000..d3479fa3 --- /dev/null +++ b/src/lib/ConnectMassaWallets/components/StationSelectAccount.tsx @@ -0,0 +1,38 @@ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore +import React from 'react'; + +import { IAccount } from '@massalabs/wallet-provider'; + +import { useAccountStore } from '../store'; +import { Dropdown } from '../../../components'; + +export function StationSelectAccount() { + const [accounts, connectedAccount, setConnectedAccount] = useAccountStore( + (state) => [ + state.accounts, + state.connectedAccount, + state.setConnectedAccount, + ], + ); + + const selectedAccountKey: number = (accounts || []).findIndex( + (account) => account.name() === connectedAccount?.name(), + ); + + const onAccountChange = async (account: IAccount) => { + setConnectedAccount(account); + }; + + return ( + { + return { + item: account.name(), + onClick: () => onAccountChange(account), + }; + })} + /> + ); +} diff --git a/src/lib/ConnectMassaWallets/components/StationWallet.tsx b/src/lib/ConnectMassaWallets/components/StationWallet.tsx new file mode 100644 index 00000000..3d420d26 --- /dev/null +++ b/src/lib/ConnectMassaWallets/components/StationWallet.tsx @@ -0,0 +1,98 @@ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore +import React from 'react'; + +import { useEffect, useState } from 'react'; + +import { + isMassaStationAvailable, + isMassaWalletEnabled, +} from '@massalabs/wallet-provider'; + +import { ConnectedAccount } from './ConnectedAccount'; +import { MASBalance } from './MASBalance'; +import { StationSelectAccount } from './StationSelectAccount'; +import { WalletError } from './WalletError'; +import { + MASSA_STATION_INSTALL, + MASSA_STATION_STORE, + MASSA_WALLET_CREATE_ACCOUNT, +} from '../../massa-react/const'; +import Intl from '../i18n'; +import { useAccountStore } from '../store'; + +export default function StationWallet() { + const { accounts } = useAccountStore(); + + const [stationIsOn, setStationIsOn] = useState( + undefined, + ); + const [massaWalletIsOn, setMassaWalletIsOn] = useState( + undefined, + ); + + useEffect(() => { + isMassaStationAvailable().then((result) => { + setStationIsOn(result); + }); + isMassaWalletEnabled().then((result) => { + setMassaWalletIsOn(result); + }); + }); + + if (stationIsOn === false) { + return ( + + ); + } + + if (massaWalletIsOn === false) { + return ( + + ); + } + + if (accounts !== undefined && !accounts.length) { + return ( + + ); + } + + if (accounts === undefined) { + return
; + } + + return ( +
+
+
+ +
+
+ +
+
+ +
+ ); +} diff --git a/src/lib/ConnectMassaWallets/components/Status/ChainStatus.tsx b/src/lib/ConnectMassaWallets/components/Status/ChainStatus.tsx new file mode 100644 index 00000000..ab0053dd --- /dev/null +++ b/src/lib/ConnectMassaWallets/components/Status/ChainStatus.tsx @@ -0,0 +1,15 @@ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore +import React from 'react'; + +import { Connected } from './Connected'; +import { Disconnected } from './Disconnected'; +import { useAccountStore } from '../../store'; + +export function ChainStatus() { + const { connectedAccount, currentProvider } = useAccountStore(); + + const connected = !!connectedAccount && !!currentProvider; + + return <>{connected ? : }; +} diff --git a/src/lib/ConnectMassaWallets/components/Status/Connected.tsx b/src/lib/ConnectMassaWallets/components/Status/Connected.tsx new file mode 100644 index 00000000..92d83a5f --- /dev/null +++ b/src/lib/ConnectMassaWallets/components/Status/Connected.tsx @@ -0,0 +1,12 @@ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore +import React from 'react'; + +import { Tag, tagTypes } from '../../../../components'; +import Intl from '../../i18n'; + +export function Connected() { + return ( + {Intl.t('connect-wallet.tag.connected')} + ); +} diff --git a/src/lib/ConnectMassaWallets/components/Status/Disconnected.tsx b/src/lib/ConnectMassaWallets/components/Status/Disconnected.tsx new file mode 100644 index 00000000..6e53eade --- /dev/null +++ b/src/lib/ConnectMassaWallets/components/Status/Disconnected.tsx @@ -0,0 +1,14 @@ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore +import React from 'react'; + +import Intl from '../../i18n'; +import { Tag, tagTypes } from '../../../../components'; + +export function Disconnected() { + return ( + + {Intl.t('connect-wallet.tag.not-connected')} + + ); +} diff --git a/src/lib/ConnectMassaWallets/components/SwitchWalletButton.tsx b/src/lib/ConnectMassaWallets/components/SwitchWalletButton.tsx new file mode 100644 index 00000000..ab165859 --- /dev/null +++ b/src/lib/ConnectMassaWallets/components/SwitchWalletButton.tsx @@ -0,0 +1,22 @@ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore +import React from 'react'; + +import Intl from '../i18n'; + +export default function SwitchWalletButton({ + onClick, +}: { + onClick: () => void; +}) { + return ( +
+

+ {Intl.t('connect-wallet.card-destination.switch')} +

+
+ ); +} diff --git a/src/lib/ConnectMassaWallets/components/WalletError.tsx b/src/lib/ConnectMassaWallets/components/WalletError.tsx new file mode 100644 index 00000000..aea80cd7 --- /dev/null +++ b/src/lib/ConnectMassaWallets/components/WalletError.tsx @@ -0,0 +1,27 @@ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore +import React from 'react'; + +interface WalletErrorProps { + description: string; + link: string; + linkLabel: string; +} + +export function WalletError(props: WalletErrorProps) { + const { description, link, linkLabel } = props; + + return ( +
+ {description} + + {linkLabel} + +
+ ); +} diff --git a/src/lib/ConnectMassaWallets/i18n/en_US.json b/src/lib/ConnectMassaWallets/i18n/en_US.json new file mode 100644 index 00000000..590b436b --- /dev/null +++ b/src/lib/ConnectMassaWallets/i18n/en_US.json @@ -0,0 +1,51 @@ +{ + "toast": { + "explorer": "view in explorer" + }, + "connect-wallet": { + "title": "Connect wallets", + "connected": "Connected wallets", + "description": "Connect wallets for {network1} and {network2}, to bridge tokens.", + "button": "Connect", + "card-destination": { + "from": "Ethereum wallet", + "to": "Massa wallet", + "non-massa-wallet": "Switch accounts directly within your wallet.", + "switch": "switch wallet", + "select-wallet": "Select wallet", + "your-wallet": "Your wallet", + "bearby-not-installed": "Bearby extension is either turned off or not installed. Make sure the extension is on and refresh the page.", + "get-bearby": "Get Bearby here", + "massa-station-not-detected": "Massa Station desktop app is not detected. Make sure the app is opened, or click below to install it.", + "get-massa-station": "Get Massa Station", + "massa-wallet-not-detected": "Massa Wallet is not detected. Make sure the plugin is installed, or click below to install it.", + "get-massa-wallet": "Get Massa Wallet", + "massa-wallet-no-account": "No wallet accounts found in Massa Wallet.", + "massa-wallet-create-account": "Create an account", + "not-detected": "wallet is not detected." + }, + "MASSASTATION": "MassaWallet", + "BEARBY": "Bearby", + "connected-cards": { + "wallet-balance": "Balance: " + }, + "connect-massa": { + "unsupported-net": "Switch to 'buildnet' in top-right corner of Station's homepage" + }, + "wrong-chain": "Wrong wallet network. Please, switch to {network} in your {name} wallet settings.", + "connect-metamask": { + "connect-to-metamask": "Connect to Metamask", + "invalid-network": "Current network is not supported. Please, switch to {network}.", + "switch-network": "Switch network", + "no-metamask": "It seems like you don't have Metamask installed.", + "install-metamask": "Install Metamask" + }, + "tag": { + "not-connected": "Not connected", + "connected": "Connected", + "wrong-chain": "Unsupported network", + "wrong-chain-massa-station-tooltip": "Switch to {network} in top-right corner of Station's homepage", + "wrong-chain-bearby-tooltip": "Switch to {network} in top-left corner of Bearby's extension" + } + } +} diff --git a/src/lib/ConnectMassaWallets/i18n/index.ts b/src/lib/ConnectMassaWallets/i18n/index.ts new file mode 100644 index 00000000..e49a2319 --- /dev/null +++ b/src/lib/ConnectMassaWallets/i18n/index.ts @@ -0,0 +1,7 @@ +import I18n from '../../i18n/i18n'; +import enUs from './en_US.json'; + +const Intl = new I18n({ EN_us: enUs }); +Object.freeze(Intl); + +export default Intl; diff --git a/src/lib/ConnectMassaWallets/index.ts b/src/lib/ConnectMassaWallets/index.ts new file mode 100644 index 00000000..f33e76b8 --- /dev/null +++ b/src/lib/ConnectMassaWallets/index.ts @@ -0,0 +1,2 @@ +export { useAccountStore } from './store'; +export { ConnectMassaWallet } from './components/ConnectMassaWallet'; diff --git a/src/lib/ConnectMassaWallets/store/accountStore.ts b/src/lib/ConnectMassaWallets/store/accountStore.ts new file mode 100644 index 00000000..c0aefc5a --- /dev/null +++ b/src/lib/ConnectMassaWallets/store/accountStore.ts @@ -0,0 +1,194 @@ +import { Client, ClientFactory } from '@massalabs/massa-web3'; +import { IAccount, IProvider } from '@massalabs/wallet-provider'; + +import { SUPPORTED_MASSA_WALLETS } from '../../massa-react/const'; + +async function handleBearbyAccountChange( + newAddress: string, + store: AccountStoreState, +) { + const { connectedAccount, currentProvider, setConnectedAccount } = store; + + const oldAddress = connectedAccount?.address(); + + if (newAddress !== oldAddress) { + const newAccounts = await currentProvider?.accounts(); + + if (newAccounts?.length) { + // Bearby returns only one account + const newAccount = newAccounts[0]; + setConnectedAccount(newAccount); + } + } +} + +export interface AccountStoreState { + connectedAccount?: IAccount; + massaClient?: Client; + accounts?: IAccount[]; + currentProvider?: IProvider; + providers: IProvider[]; + isFetching: boolean; + accountObserver?: { + unsubscribe: () => void; + }; + networkObserver?: { + unsubscribe: () => void; + }; + chainId?: bigint; + + setCurrentProvider: (provider?: IProvider) => void; + setProviders: (providers: IProvider[]) => void; + + setConnectedAccount: (account?: IAccount) => void; + refreshMassaClient: () => void; +} + +const accountStore = ( + set: (params: Partial) => void, + get: () => AccountStoreState, +) => ({ + accounts: undefined, + connectedAccount: undefined, + accountObserver: undefined, + networkObserver: undefined, + massaClient: undefined, + currentProvider: undefined, + providers: [], + isFetching: false, + chainId: undefined, + + setCurrentProvider: (currentProvider?: IProvider) => { + try { + set({ isFetching: true }); + + const previousProvider = get().currentProvider; + + if (previousProvider?.name() !== currentProvider?.name()) { + get().accountObserver?.unsubscribe(); + get().networkObserver?.unsubscribe(); + set({ accountObserver: undefined, networkObserver: undefined }); + } + if (!currentProvider) { + set({ + currentProvider: undefined, + connectedAccount: undefined, + accounts: undefined, + }); + return; + } + + if (!get().networkObserver) { + const networkObserver = currentProvider.listenNetworkChanges( + async () => { + get().refreshMassaClient(); + set({ chainId: await currentProvider.getChainId() }); + }, + ); + set({ networkObserver }); + } + + if (currentProvider?.name() === SUPPORTED_MASSA_WALLETS.BEARBY) { + currentProvider + .connect() + .then(() => { + // get current network + currentProvider + .getChainId() + .then((chainId) => { + set({ chainId }); + }) + .catch((error) => { + console.warn('error getting network from bearby', error); + }); + // subscribe to network events + const observer = currentProvider.listenAccountChanges( + (newAddress: string) => { + handleBearbyAccountChange(newAddress, get()); + }, + ); + set({ currentProvider, accountObserver: observer }); + + // get connected account + currentProvider + .accounts() + .then((accounts) => { + // bearby expose only 1 account + get().setConnectedAccount(accounts[0]); + set({ accounts }); + }) + .catch((error) => { + console.warn('error getting accounts from bearby', error); + }); + }) + .catch((error) => { + console.warn('error connecting to bearby', error); + }); + return; + } + + set({ currentProvider }); + + currentProvider + .accounts() + .then((accounts) => { + set({ accounts }); + + const selectedAccount = accounts[0]; + get().setConnectedAccount(selectedAccount); + }) + .catch((error) => { + console.warn('error getting accounts from provider', error); + }); + } finally { + set({ isFetching: false }); + } + }, + + setProviders: (providers: IProvider[]) => { + set({ providers }); + + // if current provider is not in the new list of providers, unset it + if (!providers.some((p) => p.name() === get().currentProvider?.name())) { + set({ + massaClient: undefined, + currentProvider: undefined, + connectedAccount: undefined, + accounts: undefined, + }); + } + }, + + // set the connected account, and update the massa client + setConnectedAccount: async (connectedAccount?: IAccount) => { + set({ connectedAccount }); + if (connectedAccount) { + const currentProvider = get().currentProvider; + if (!currentProvider) throw new Error('No provider found'); + const provider = currentProvider; + // update the massa client with the new account + set({ + massaClient: await ClientFactory.fromWalletProvider( + provider, + connectedAccount, + ), + }); + } + }, + + refreshMassaClient: async () => { + const provider = get().currentProvider; + if (!provider) return; + + const connectedAccount = get().connectedAccount; + if (!connectedAccount) return; + set({ + massaClient: await ClientFactory.fromWalletProvider( + provider, + connectedAccount, + ), + }); + }, +}); + +export default accountStore; diff --git a/src/lib/ConnectMassaWallets/store/index.ts b/src/lib/ConnectMassaWallets/store/index.ts new file mode 100644 index 00000000..3e68babe --- /dev/null +++ b/src/lib/ConnectMassaWallets/store/index.ts @@ -0,0 +1,20 @@ +import { ProvidersListener } from '@massalabs/wallet-provider'; +import { create } from 'zustand'; + +import accountStore, { AccountStoreState } from './accountStore'; + +export const useAccountStore = create((set, get) => ({ + ...accountStore(set, get), +})); + +async function initAccountStore() { + new ProvidersListener(4_000).subscribe((providers) => { + useAccountStore.getState().setProviders(providers); + }); +} + +async function initializeStores() { + await initAccountStore(); +} + +initializeStores(); diff --git a/src/lib/README.MD b/src/lib/README.MD new file mode 100644 index 00000000..dcf48c30 --- /dev/null +++ b/src/lib/README.MD @@ -0,0 +1,47 @@ +# Massa React UI-KIT libraries + +This directory contains the React UI-KIT libraries that are used in Massa projects. + +## i18n + +**You can use it to translate the text in the project.** + +Use it like this: + +```typescript +import I18n from '@massalabs/react-ui-kit/lib/i18n'; +// create a json file to store the text you want to translate: +import enUs from './en_US.json'; + +const Intl = new I18n({ EN_us: enUs }); +Object.freeze(Intl); + +export default Intl; +``` + +See the example in the `ConnectMassaWallets` lib. + +Future development: allow to change the language in the dapp. + +## ConnectMassaWallets + +- contains components that are used to connect to Massa wallets. +- contains a store that you can use in your dapp. +- use the lib i18n to translate the text in the components. + +## massa-react + +**Oriented to the interactions with Massa blockchain.** + +- contains hooks that you can use in your dapp. +- contains utility functions that you can use in your dapp. +- contains constants that you can use in your dapp. +- use the lib i18n to translate the text in the hooks. + +## Util + +**General UI utility functions.** + +- contains utility functions to format amount +- contains utility functions that you can use in your dapp. +- contains hooks that you can use in your dapp. diff --git a/src/lib/i18n/i18n.ts b/src/lib/i18n/i18n.ts new file mode 100644 index 00000000..710ae627 --- /dev/null +++ b/src/lib/i18n/i18n.ts @@ -0,0 +1,47 @@ +import dot from 'dot-object'; + +type TranslationObject = string | { [key: string]: TranslationObject }; + +export type Copy = Record; +export type Language = string; +export type Keys = Record; + +class I18n { + private lang: Language; + private copy: Copy; + + constructor(keys: Keys, lang = 'EN_us') { + this.lang = lang; + this.copy = keys[this.lang]; + } + + public t(key: string, interpolations?: Record): string { + const copy = this.copy; + // we are using pick in order to make life easier when the day for plurals and copy with params arrives + const result = dot.pick(key, copy); + + if (!result) { + console.warn(`I18n::t:: No translation found for key ${key}`); + } + + return interpolations + ? this._interpolateKeys(result, interpolations) + : result ?? key; + } + + private _interpolateKeys( + str: string, + replacements: Record, + char1 = '{', + char2 = '}', + ): string { + const regex = new RegExp(`${char1}[^${char2}]*${char2}`, 'g'); + + return str.replace(regex, (match) => { + const key = match.slice(1, -1); + return replacements[key] ?? match; + }); + } +} + +export default I18n; diff --git a/src/lib/massa-react/const.ts b/src/lib/massa-react/const.ts new file mode 100644 index 00000000..7bf80ea6 --- /dev/null +++ b/src/lib/massa-react/const.ts @@ -0,0 +1,17 @@ +export const MASSA = 'Massa'; + +export const MASSA_STATION_INSTALL = 'https://station.massa.net'; +export const MASSA_STATION_STORE = 'https://station.massa/web/store'; +export const MASSA_WALLET_CREATE_ACCOUNT = + 'http://station.massa/plugin/massa-labs/massa-wallet/web-app/account-create'; +export const massaToken = 'MAS'; +export const BEARBY_INSTALL = 'https://bearby.io'; +export enum SUPPORTED_MASSA_WALLETS { + MASSASTATION = 'MASSASTATION', + BEARBY = 'BEARBY', +} + +export const MASSA_EXPLO_URL = 'https://massexplo.io/tx/'; +export const MASSA_EXPLO_EXTENSION = '?network=buildnet'; +export const MASSA_EXPLORER_URL = + 'https://explorer.massa.net/mainnet/operation/'; diff --git a/src/lib/massa-react/hooks/useWriteSmartContract.tsx b/src/lib/massa-react/hooks/useWriteSmartContract.tsx new file mode 100644 index 00000000..30cd6f49 --- /dev/null +++ b/src/lib/massa-react/hooks/useWriteSmartContract.tsx @@ -0,0 +1,169 @@ +import { useState } from 'react'; + +import { + Client, + EOperationStatus, + ICallData, + MAX_GAS_CALL, +} from '@massalabs/massa-web3'; +import { toast, ToastContent } from '../../../components'; +import { OperationToast } from '../../ConnectMassaWallets/components/OperationToast'; +import { logSmartContractEvents } from '../utils'; +import Intl from '../i18n'; + +interface ToasterMessage { + pending: string; + success: string; + error: string; + timeout?: string; +} + +function minBigInt(a: bigint, b: bigint) { + return a < b ? a : b; +} + +export function useWriteSmartContract(client?: Client, isMainnet?: boolean) { + const [isPending, setIsPending] = useState(false); + const [isOpPending, setIsOpPending] = useState(false); + const [isSuccess, setIsSuccess] = useState(false); + const [isError, setIsError] = useState(false); + const [opId, setOpId] = useState(undefined); + + function callSmartContract( + targetFunction: string, + targetAddress: string, + parameter: number[], + messages: ToasterMessage, + coins = BigInt(0), + ) { + if (!client) { + throw new Error('Massa client not found'); + } + if (isOpPending) { + throw new Error('Operation is already pending'); + } + setIsSuccess(false); + setIsError(false); + setIsOpPending(false); + setIsPending(true); + let operationId: string | undefined; + let toastId: string | undefined; + + const callData = { + targetAddress, + targetFunction, + parameter, + coins, + } as ICallData; + + client + .smartContracts() + .readSmartContract(callData) + .then((response) => { + const gasCost = BigInt(response.info.gas_cost); + return minBigInt(gasCost + (gasCost * 20n) / 100n, MAX_GAS_CALL); + }) + .then((maxGas: bigint) => { + callData.maxGas = maxGas; + return client.smartContracts().callSmartContract(callData); + }) + .then((opId) => { + operationId = opId; + setOpId(operationId); + setIsOpPending(true); + toastId = toast.loading( + (t) => ( + + + + ), + { + duration: Infinity, + }, + ); + return client + .smartContracts() + .awaitMultipleRequiredOperationStatus(operationId, [ + EOperationStatus.SPECULATIVE_ERROR, + EOperationStatus.FINAL_ERROR, + EOperationStatus.FINAL_SUCCESS, + ]); + }) + .then((status: EOperationStatus) => { + if (status !== EOperationStatus.FINAL_SUCCESS) { + throw new Error('Operation failed', { cause: { status } }); + } + setIsSuccess(true); + setIsOpPending(false); + setIsPending(false); + toast.dismiss(toastId); + toast.success((t) => ( + + + + )); + }) + .catch((error) => { + console.error(error); + toast.dismiss(toastId); + setIsError(true); + setIsOpPending(false); + setIsPending(false); + + if (!operationId) { + console.error('Operation ID not found'); + toast.error((t) => ( + + + + )); + return; + } + + if ( + [ + EOperationStatus.FINAL_ERROR, + EOperationStatus.SPECULATIVE_ERROR, + ].includes(error.cause?.status) + ) { + toast.error((t) => ( + + + + )); + logSmartContractEvents(client, operationId); + } else { + toast.error((t) => ( + + + + )); + } + }); + } + + return { + opId, + isOpPending, + isPending, + isSuccess, + isError, + callSmartContract, + }; +} diff --git a/src/lib/massa-react/i18n/en_US.json b/src/lib/massa-react/i18n/en_US.json new file mode 100644 index 00000000..8a9411ed --- /dev/null +++ b/src/lib/massa-react/i18n/en_US.json @@ -0,0 +1,8 @@ +{ + "steps": { + "failed-timeout": "Operation failed due to timeout, please retry with more fees." + }, + "balance": { + "error": "We couldn't fetch your balance! Please, try again!" + } +} diff --git a/src/lib/massa-react/i18n/index.ts b/src/lib/massa-react/i18n/index.ts new file mode 100644 index 00000000..e49a2319 --- /dev/null +++ b/src/lib/massa-react/i18n/index.ts @@ -0,0 +1,7 @@ +import I18n from '../../i18n/i18n'; +import enUs from './en_US.json'; + +const Intl = new I18n({ EN_us: enUs }); +Object.freeze(Intl); + +export default Intl; diff --git a/src/lib/massa-react/utils.ts b/src/lib/massa-react/utils.ts new file mode 100644 index 00000000..cb3a5ee1 --- /dev/null +++ b/src/lib/massa-react/utils.ts @@ -0,0 +1,72 @@ +import { Client } from '@massalabs/massa-web3'; +import { IAccount, IAccountBalanceResponse } from '@massalabs/wallet-provider'; + +import { + MASSA_EXPLO_EXTENSION, + MASSA_EXPLO_URL, + MASSA_EXPLORER_URL, +} from './const'; +import Intl from './i18n'; +import { toast } from '../../components'; + +export function logSmartContractEvents( + client: Client, + operationId: string, +): void { + client + .smartContracts() + .getFilteredScOutputEvents({ + emitter_address: null, + start: null, + end: null, + original_caller_address: null, + original_operation_id: operationId, + is_final: null, + }) + .then((events) => { + events.map((l) => + console.error(`opId ${operationId}: execution error ${l.data}`), + ); + }); +} + +export function generateExplorerLink(opId: string, isMainnet = true): string { + const buildnetExplorerUrl = `${MASSA_EXPLO_URL}${opId}${MASSA_EXPLO_EXTENSION}`; + const mainnetExplorerUrl = `${MASSA_EXPLORER_URL}${opId}`; + const explorerUrl = isMainnet ? mainnetExplorerUrl : buildnetExplorerUrl; + + return explorerUrl; +} + +/** + * Masks the middle of an address with a specified character. + * @param str - The address to mask. + * @param mask - The character to use for masking. Defaults to `.`. + * @returns The masked address. + */ +export function maskAddress(str: string, length = 4, mask = '...'): string { + const start = length; + const end = str?.length - length; + + return str ? str?.substring(0, start) + mask + str?.substring(end) : ''; +} + +export function maskNickname(str: string, length = 32): string { + if (!str) return ''; + + if (str.length <= length) return str; + + return str?.substring(0, length) + '...'; +} + +export async function fetchMASBalance( + account: IAccount, +): Promise { + try { + return account.balance(); + } catch (error) { + console.error('Error while retrieving balance: ', error); + toast.error(Intl.t('balance.error')); + return { finalBalance: '0', candidateBalance: '0' }; + } +} diff --git a/src/util/useClickOutside.ts b/src/lib/util/hooks/useClickOutside.ts similarity index 100% rename from src/util/useClickOutside.ts rename to src/lib/util/hooks/useClickOutside.ts diff --git a/src/util/useLocalStorage.ts b/src/lib/util/hooks/useLocalStorage.ts similarity index 100% rename from src/util/useLocalStorage.ts rename to src/lib/util/hooks/useLocalStorage.ts diff --git a/src/util/parseAmount.test.ts b/src/lib/util/parseAmount.test.ts similarity index 59% rename from src/util/parseAmount.test.ts rename to src/lib/util/parseAmount.test.ts index 9cb06a13..66e5dd4a 100644 --- a/src/util/parseAmount.test.ts +++ b/src/lib/util/parseAmount.test.ts @@ -1,5 +1,6 @@ import { formatAmount, + formatStandard, roundDecimalPartToOneSignificantDigit, } from './parseAmount'; @@ -86,3 +87,75 @@ describe('roundDecimalPartToOneSignificantDigit', () => { expect(roundDecimalPartToOneSignificantDigit('0099')).toEqual('01'); }); }); + +describe('formatStandard', () => { + test('formats an empty string', () => { + const result = formatStandard('', 18); + expect(result).toEqual('0'); + }); + + test('formats an amount with default parameters', () => { + const result = formatStandard('123456789012345678901', 18); + expect(result).toEqual('123.456789012345678901'); + }); + + test('formats an amount with less than the specified decimals', () => { + const result = formatStandard('12345', 8); + expect(result).toEqual('0.00012345'); + }); + + test('adds padding zeroes when necessary', () => { + const result = formatStandard('1', 18); + expect(result).toEqual('0.000000000000000001'); + }); + + test('handles amount with exact decimals length', () => { + const result = formatStandard('1000000000000000000', 18); + expect(result).toEqual('1'); + }); + + test('formats an amount with less than the specified decimals and round up', () => { + const result = formatStandard('69000', 9); + expect(result).toEqual('0.000069'); + }); + + it('formatStandard with min string value', () => { + const value = '0000000000'; + + const result = formatStandard(value.toString()); + + expect(result).toBe('0'); + }); + + it('formatStandard with min bigint value', () => { + const value = 0n; + + const result = formatStandard(value.toString()); + + expect(result).toBe('0'); + }); + + it('formatStandard with mid range string value', () => { + const value = '10000000000000'; + + const result = formatStandard(value.toString()); + + expect(result).toBe('10,000'); + }); + + it('formatStandard with mid range bigint value', () => { + const value = 10000000000000n; + + const result = formatStandard(value.toString()); + + expect(result).toBe('10,000'); + }); + + it('formatStandard with max string value', () => { + const value = '922337203600000000000'; + + const result = formatStandard(value.toString()); + + expect(result).toBe('922,337,203,600'); + }); +}); diff --git a/src/util/parseAmount.ts b/src/lib/util/parseAmount.ts similarity index 92% rename from src/util/parseAmount.ts rename to src/lib/util/parseAmount.ts index b6313f33..80dc61b6 100644 --- a/src/util/parseAmount.ts +++ b/src/lib/util/parseAmount.ts @@ -7,6 +7,17 @@ export interface FormattedAmount { amountFormattedFull: string; } +function removeTrailingZeros(numStr: string): string { + return numStr.replace(/\.?0+$/, ''); +} + +// Like format amount but remove the trailing zeros +export function formatStandard(amount: string, decimals = 9): string { + return removeTrailingZeros( + formatAmount(amount, decimals).amountFormattedFull, + ); +} + /** * reverse format FT amount */ diff --git a/src/util/truncate.ts b/src/lib/util/truncate.ts similarity index 100% rename from src/util/truncate.ts rename to src/lib/util/truncate.ts diff --git a/src/util/utils.ts b/src/lib/util/utils.ts similarity index 100% rename from src/util/utils.ts rename to src/lib/util/utils.ts diff --git a/src/util/types.ts b/src/util/types.ts deleted file mode 100644 index fd8dccf3..00000000 --- a/src/util/types.ts +++ /dev/null @@ -1 +0,0 @@ -export type Theme = 'theme-light' | 'theme-dark'; diff --git a/tsconfig.json b/tsconfig.json index 85f3f678..a0a3a462 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,6 +4,7 @@ "lib": ["DOM", "DOM.Iterable", "ESNext"], "module": "ESNext", "skipLibCheck": true, + "esModuleInterop": true, "moduleResolution": "node", "allowSyntheticDefaultImports": true, diff --git a/vite.config.ts b/vite.config.ts index a46ad84f..927f2438 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -19,5 +19,8 @@ export default ({ mode }) => { exportAsDefault: true, }), ], + optimizeDeps: { + include: ['react-dom', 'dot-object', 'copy-to-clipboard'], + }, }); };