diff --git a/cypress.config.js b/cypress.config.js index 1e7568e5..bfc84190 100644 --- a/cypress.config.js +++ b/cypress.config.js @@ -5,8 +5,12 @@ export default defineConfig({ specPattern: 'tests/e2e/**/*.{cy,spec}.{js,jsx,ts,tsx}', baseUrl: 'http://localhost:5173', supportFile: 'tests/e2e/support/index.js', - chromeWebSecurity: false, - screenshotOnRunFailure: false, - video: false, }, + chromeWebSecurity: false, + video: false, + screenshotOnRunFailure: false, + userAgent: + 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4.1 Mobile/15E148 Safari/604.1', + viewportHeight: 800, + viewportWidth: 400, }); diff --git a/package-lock.json b/package-lock.json index d759e9a9..fe8e2d70 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,9 +15,13 @@ "@sentry/vue": "^7.101.0", "@vueuse/head": "^2.0.0", "bignumber.js": "^9.1.2", + "chart.js": "^4.4.3", + "chartjs-adapter-date-fns": "^3.0.0", + "date-fns": "^3.6.0", "dex-contracts-v2": "github:aeternity/dex-contracts-v2#69f800fece02da1444af7f0d6622fec356a15496", "node-polyfill-webpack-plugin": "^3.0.0", "vue": "^3.4.19", + "vue-chartjs": "^5.3.1", "vue-i18n": "^9.9.1", "vue-router": "^4.2.5", "vuex": "^4.1.0", @@ -30,6 +34,7 @@ "@vue/eslint-config-airbnb": "^8.0.0", "@vue/eslint-config-prettier": "^9.0.0", "@vue/test-utils": "^2.4.4", + "autoprefixer": "^10.4.19", "cypress": "^13.6.4", "eslint": "^8.2.0", "eslint-plugin-cypress": "^2.15.1", @@ -37,12 +42,14 @@ "eslint-plugin-vue": "^9.21.1", "file-loader": "^6.2.0", "jsdom": "^24.0.0", + "postcss": "^8.4.38", "prettier": "^3.2.5", "sass": "^1.70.0", "start-server-and-test": "^2.0.3", "stylelint": "^16.2.1", "stylelint-config-recommended-vue": "^1.5.0", "stylelint-config-standard-scss": "^13.0.0", + "tailwindcss": "^3.4.4", "vite": "^5.1.3", "vite-svg-loader": "^5.1.0", "vitest": "^1.2.2" @@ -139,6 +146,18 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@azure/abort-controller": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", @@ -1151,7 +1170,6 @@ "version": "0.3.3", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", - "peer": true, "dependencies": { "@jridgewell/set-array": "^1.0.1", "@jridgewell/sourcemap-codec": "^1.4.10", @@ -1165,7 +1183,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", - "peer": true, "engines": { "node": ">=6.0.0" } @@ -1174,7 +1191,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "peer": true, "engines": { "node": ">=6.0.0" } @@ -1198,12 +1214,16 @@ "version": "0.3.22", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz", "integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==", - "peer": true, "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@kurkle/color": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz", + "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==" + }, "node_modules/@ledgerhq/devices": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/@ledgerhq/devices/-/devices-8.3.0.tgz", @@ -2495,6 +2515,12 @@ "node": ">=4" } }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true + }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -2840,6 +2866,43 @@ "node": ">= 4.0.0" } }, + "node_modules/autoprefixer": { + "version": "10.4.19", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz", + "integrity": "sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "browserslist": "^4.23.0", + "caniuse-lite": "^1.0.30001599", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.0", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, "node_modules/available-typed-arrays": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.6.tgz", @@ -3128,9 +3191,9 @@ } }, "node_modules/browserslist": { - "version": "4.22.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.3.tgz", - "integrity": "sha512-UAp55yfwNv0klWNapjs/ktHoguxuQNGnOzxYmfnXIS+8AsRDZkSDxg7R1AX3GKzn078SBI5dzwzj/Yx0Or0e3A==", + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", + "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", "funding": [ { "type": "opencollective", @@ -3145,10 +3208,9 @@ "url": "https://github.com/sponsors/ai" } ], - "peer": true, "dependencies": { - "caniuse-lite": "^1.0.30001580", - "electron-to-chromium": "^1.4.648", + "caniuse-lite": "^1.0.30001587", + "electron-to-chromium": "^1.4.668", "node-releases": "^2.0.14", "update-browserslist-db": "^1.0.13" }, @@ -3272,10 +3334,19 @@ "node": ">=6" } }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, "node_modules/caniuse-lite": { - "version": "1.0.30001587", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001587.tgz", - "integrity": "sha512-HMFNotUmLXn71BQxg8cijvqxnIAofforZOwGsxyXJ0qugTdspUF4sPSJ2vhgprHCB996tIDzEq1ubumPDV8ULA==", + "version": "1.0.30001629", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001629.tgz", + "integrity": "sha512-c3dl911slnQhmxUIT4HhYzT7wnBK/XYpGnYLOj4nJBaRiw52Ibe7YxlDaAeRECvA786zCuExhxIUJ2K7nHMrBw==", "funding": [ { "type": "opencollective", @@ -3289,8 +3360,7 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ], - "peer": true + ] }, "node_modules/canonicalize": { "version": "2.0.0", @@ -3335,6 +3405,26 @@ "node": ">=4" } }, + "node_modules/chart.js": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.3.tgz", + "integrity": "sha512-qK1gkGSRYcJzqrrzdR6a+I0vQ4/R+SoODXyAjscQ/4mzuNzySaMCd+hyVxitSY1+L2fjPD1Gbn+ibNqRmwQeLw==", + "dependencies": { + "@kurkle/color": "^0.3.0" + }, + "engines": { + "pnpm": ">=8" + } + }, + "node_modules/chartjs-adapter-date-fns": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chartjs-adapter-date-fns/-/chartjs-adapter-date-fns-3.0.0.tgz", + "integrity": "sha512-Rs3iEB3Q5pJ973J93OBTpnP7qoGwvq3nUnoMdtxO+9aoJof7UFcRbWcIDteXuYd1fgAvct/32T9qaLyLuZVwCg==", + "peerDependencies": { + "chart.js": ">=2.8.0", + "date-fns": ">=2.0.0" + } + }, "node_modules/check-error": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", @@ -4073,6 +4163,15 @@ "node": ">=18" } }, + "node_modules/date-fns": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz", + "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, "node_modules/dayjs": { "version": "1.11.10", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", @@ -4210,6 +4309,12 @@ "fs": "^0.0.1-security" } }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true + }, "node_modules/diff-sequences": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", @@ -4246,6 +4351,12 @@ "node": ">=8" } }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true + }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -4413,10 +4524,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.667", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.667.tgz", - "integrity": "sha512-66L3pLlWhTNVUhnmSA5+qDM3fwnXsM6KAqE36e2w4KN0g6pkEtlT5bs41FQtQwVwKnfhNBXiWRLPs30HSxd7Kw==", - "peer": true + "version": "1.4.792", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.792.tgz", + "integrity": "sha512-rkg5/N3L+Y844JyfgPUyuKK0Hk0efo3JNxUDKvz3HgP6EmN4rNGhr2D8boLsfTV/hGo7ZGAL8djw+jlg99zQyA==" }, "node_modules/elliptic": { "version": "6.5.4", @@ -4741,7 +4851,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", - "peer": true, "engines": { "node": ">=6" } @@ -5976,6 +6085,19 @@ "node": ">= 0.12" } }, + "node_modules/fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "dev": true, + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, "node_modules/from": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", @@ -7219,6 +7341,15 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/jiti": { + "version": "1.21.3", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.3.tgz", + "integrity": "sha512-uy2bNX5zQ+tESe+TiC7ilGRz8AtRGmnJH55NC5S0nSUjvvvM2hJHmefHErugGXN4pNv4Qx7vLsnNw9qJ9mtIsw==", + "dev": true, + "bin": { + "jiti": "bin/jiti.js" + } + }, "node_modules/joi": { "version": "17.12.1", "resolved": "https://registry.npmjs.org/joi/-/joi-17.12.1.tgz", @@ -7573,6 +7704,15 @@ "node": ">= 0.8.0" } }, + "node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -8066,6 +8206,17 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, "node_modules/nanoid": { "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", @@ -8204,8 +8355,7 @@ "node_modules/node-releases": { "version": "2.0.14", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", - "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", - "peer": true + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==" }, "node_modules/nopt": { "version": "7.2.0", @@ -8231,6 +8381,15 @@ "node": ">=0.10.0" } }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", @@ -8280,6 +8439,15 @@ "node": ">=0.10.0" } }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, "node_modules/object-inspect": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", @@ -8580,16 +8748,16 @@ "dev": true }, "node_modules/path-scurry": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", - "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "dev": true, "dependencies": { - "lru-cache": "^9.1.1 || ^10.0.0", + "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=16 || 14 >=14.18" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -8699,6 +8867,15 @@ "node": ">=0.10.0" } }, + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, "node_modules/pkg-types": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.0.3.tgz", @@ -8711,9 +8888,9 @@ } }, "node_modules/postcss": { - "version": "8.4.35", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz", - "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==", + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", "funding": [ { "type": "opencollective", @@ -8731,7 +8908,7 @@ "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "source-map-js": "^1.2.0" }, "engines": { "node": "^10 || ^12 || >=14" @@ -8760,12 +8937,114 @@ "dev": true, "peer": true }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", + "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", + "dev": true, + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", + "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "lilconfig": "^3.0.0", + "yaml": "^2.3.4" + }, + "engines": { + "node": ">= 14" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-load-config/node_modules/lilconfig": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.1.tgz", + "integrity": "sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, "node_modules/postcss-media-query-parser": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz", "integrity": "sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==", "dev": true }, + "node_modules/postcss-nested": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz", + "integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.11" + }, + "engines": { + "node": ">=12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, "node_modules/postcss-resolve-nested-selector": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.1.tgz", @@ -9074,6 +9353,15 @@ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", "dev": true }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "dependencies": { + "pify": "^2.3.0" + } + }, "node_modules/readable-stream": { "version": "4.5.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", @@ -9602,9 +9890,9 @@ } }, "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", "engines": { "node": ">=0.10.0" } @@ -10354,6 +10642,110 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/sucrase": { + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", + "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "^10.3.10", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/sucrase/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/sucrase/node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/sucrase/node_modules/glob": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.1.tgz", + "integrity": "sha512-2jelhlq3E4ho74ZyVLN03oKdAZVUa6UDZzFLVH1H7dnoax+y9qyaq8zBkfDIggjniU19z0wU18y16jMB2eyVIw==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sucrase/node_modules/jackspeak": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.0.tgz", + "integrity": "sha512-JVYhQnN59LVPFCEcVa2C3CrEKYacvjRfqIQl+h8oi91aLYQVWRYbxjPcv1bUiUy/kLmQaANrYfNMCO3kuEDHfw==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/sucrase/node_modules/minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sucrase/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/sugarss": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/sugarss/-/sugarss-2.0.0.tgz", @@ -10616,6 +11008,43 @@ "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, + "node_modules/tailwindcss": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.4.tgz", + "integrity": "sha512-ZoyXOdJjISB7/BcLTR6SEsLgKtDStYyYZVLsUtWChO4Ps20CBad7lfJKVDiejocV4ME1hLmyY0WJE3hSDcmQ2A==", + "dev": true, + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.5.3", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.0", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.0", + "lilconfig": "^2.1.0", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.23", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.1", + "postcss-nested": "^6.0.1", + "postcss-selector-parser": "^6.0.11", + "resolve": "^1.22.2", + "sucrase": "^3.32.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", @@ -10750,6 +11179,27 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/throttleit": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.1.tgz", @@ -10860,6 +11310,12 @@ "node": ">=18" } }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true + }, "node_modules/tsconfig-paths": { "version": "3.15.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", @@ -11115,7 +11571,6 @@ "url": "https://github.com/sponsors/ai" } ], - "peer": true, "dependencies": { "escalade": "^3.1.1", "picocolors": "^1.0.0" @@ -11413,6 +11868,15 @@ } } }, + "node_modules/vue-chartjs": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/vue-chartjs/-/vue-chartjs-5.3.1.tgz", + "integrity": "sha512-rZjqcHBxKiHrBl0CIvcOlVEBwRhpWAVf6rDU3vUfa7HuSRmGtCslc0Oc8m16oAVuk0erzc1FCtH1VCriHsrz+A==", + "peerDependencies": { + "chart.js": "^4.1.1", + "vue": "^3.0.0-0 || ^2.7.0" + } + }, "node_modules/vue-component-type-helpers": { "version": "1.8.27", "resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-1.8.27.tgz", @@ -12027,6 +12491,18 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, + "node_modules/yaml": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.3.tgz", + "integrity": "sha512-sntgmxj8o7DE7g/Qi60cqpLBA3HG3STcDA0kO+WfB05jEKhZMbY7umNm2rBpQvsmZ16/lPXCJGW2672dgOUkrg==", + "dev": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/yauzl": { "version": "2.10.0", "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", diff --git a/package.json b/package.json index 63945b37..600ef07e 100644 --- a/package.json +++ b/package.json @@ -8,8 +8,8 @@ "build": "vite build", "preview": "vite preview", "test:unit": "vitest", - "test:e2e": "start-server-and-test 'vite --host' http://localhost:5173 'cypress run --e2e'", - "test:e2e:dev": "start-server-and-test 'vite' http://localhost:5173 'cypress open --e2e'", + "test:e2e": "start-server-and-test 'vite --mode test --host' http://localhost:5173 'cypress run --e2e'", + "test:e2e:dev": "start-server-and-test 'vite --mode test' http://localhost:5173 'cypress open --e2e'", "lint:script": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --ignore-path .gitignore", "lint:style": "stylelint \"src/**/*.{css,scss,vue}\"", "lint": "npm run lint:script && npm run lint:style", @@ -24,9 +24,13 @@ "@sentry/vue": "^7.101.0", "@vueuse/head": "^2.0.0", "bignumber.js": "^9.1.2", + "chart.js": "^4.4.3", + "chartjs-adapter-date-fns": "^3.0.0", + "date-fns": "^3.6.0", "dex-contracts-v2": "github:aeternity/dex-contracts-v2#69f800fece02da1444af7f0d6622fec356a15496", "node-polyfill-webpack-plugin": "^3.0.0", "vue": "^3.4.19", + "vue-chartjs": "^5.3.1", "vue-i18n": "^9.9.1", "vue-router": "^4.2.5", "vuex": "^4.1.0", @@ -39,6 +43,7 @@ "@vue/eslint-config-airbnb": "^8.0.0", "@vue/eslint-config-prettier": "^9.0.0", "@vue/test-utils": "^2.4.4", + "autoprefixer": "^10.4.19", "cypress": "^13.6.4", "eslint": "^8.2.0", "eslint-plugin-cypress": "^2.15.1", @@ -46,12 +51,14 @@ "eslint-plugin-vue": "^9.21.1", "file-loader": "^6.2.0", "jsdom": "^24.0.0", + "postcss": "^8.4.38", "prettier": "^3.2.5", "sass": "^1.70.0", "start-server-and-test": "^2.0.3", "stylelint": "^16.2.1", "stylelint-config-recommended-vue": "^1.5.0", "stylelint-config-standard-scss": "^13.0.0", + "tailwindcss": "^3.4.4", "vite": "^5.1.3", "vite-svg-loader": "^5.1.0", "vitest": "^1.2.2" diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 00000000..2aa7205d --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/src/App.vue b/src/App.vue index bde34cc6..bbffae97 100644 --- a/src/App.vue +++ b/src/App.vue @@ -38,6 +38,7 @@ export default { data() { return { backendSanityCheckTimeoutId: null, + walletExploreDelayed: false, }; }, computed: { @@ -51,6 +52,12 @@ export default { await this.checkBackendStatus(); } }, + $route(to) { + if (!to.path.includes('/explore') && this.walletExploreDelayed) { + this.walletExploreDelayed = false; + this.$store.dispatch('connectWallet', { info: this.wallet }); + } + }, }, async mounted() { if (this.$store.state.lang) this.$i18n.locale = this.$store.state.lang; @@ -60,6 +67,7 @@ export default { const query = { // safari vue-router issue address: new URLSearchParams(window.location.search).get('address'), + networkId: new URLSearchParams(window.location.search).get('networkId'), ...this.$route.query, }; @@ -98,7 +106,11 @@ export default { delete query.networkId; this.$router.replace({ query }); } else if (this.wallet && this.address) { - await this.$store.dispatch('connectWallet', { info: this.wallet }); + if (this.$route.fullPath.includes('/explore')) { + this.walletExploreDelayed = true; + } else { + await this.$store.dispatch('connectWallet', { info: this.wallet }); + } } if ( this.$isMobile && diff --git a/src/components/AccountInfoModal.vue b/src/components/AccountInfoModal.vue index 538cec44..b240e8e2 100644 --- a/src/components/AccountInfoModal.vue +++ b/src/components/AccountInfoModal.vue @@ -23,7 +23,7 @@
- + {{ `${address.slice(0, 6)}...${address.slice(-3)}` }}
@@ -126,6 +126,7 @@ import AeLogo from '@/assets/logo.svg'; import AeLogoSmall from '@/assets/logo-small.svg'; import BackArrow from '@/assets/back.svg'; import AnimatedSpinner from '@/assets/animated-spinner.svg'; +import AddressAvatar from '@/components/AddressAvatar.vue'; import ActionsMenu from './ActionsMenu.vue'; import AeBalance from './AeBalance.vue'; import NavigationMenu from './NavigationMenu.vue'; @@ -134,6 +135,7 @@ import ButtonPlain from './ButtonPlain.vue'; export default { components: { + AddressAvatar, AeLogo, AeLogoSmall, BackArrow, diff --git a/src/components/InfoRow.vue b/src/components/InfoRow.vue index 568a5131..ee8cff18 100644 --- a/src/components/InfoRow.vue +++ b/src/components/InfoRow.vue @@ -36,7 +36,6 @@ export default { span:last-of-type { margin-right: 4px; - white-space: nowrap; } } diff --git a/src/components/NavigationMenu.vue b/src/components/NavigationMenu.vue index 73256444..ab04b0d5 100644 --- a/src/components/NavigationMenu.vue +++ b/src/components/NavigationMenu.vue @@ -3,9 +3,12 @@ {{ $t('nav.swap') }} - + {{ $t('nav.pool') }} + + {{ $t('nav.explore') }} + @@ -106,6 +130,13 @@ export default { @use '../../styles/typography.scss'; @use './style.scss'; +.button-show-more { + margin: 15px 0; + color: variables.$color-white; + + @extend %face-sans-16-regular; +} + .list-tokens { display: flex; flex-direction: column; diff --git a/src/components/TokenSelector/TokenImportCard.vue b/src/components/TokenSelector/TokenImportCard.vue index d1c1bb46..b1be269d 100644 --- a/src/components/TokenSelector/TokenImportCard.vue +++ b/src/components/TokenSelector/TokenImportCard.vue @@ -6,7 +6,7 @@
- +
{{ token.symbol }}
@@ -32,10 +32,12 @@ + + diff --git a/src/components/explore/DividerLine.vue b/src/components/explore/DividerLine.vue new file mode 100644 index 00000000..3fb4edba --- /dev/null +++ b/src/components/explore/DividerLine.vue @@ -0,0 +1,9 @@ + + + diff --git a/src/components/explore/ExploreWrapper.vue b/src/components/explore/ExploreWrapper.vue new file mode 100644 index 00000000..01c6f371 --- /dev/null +++ b/src/components/explore/ExploreWrapper.vue @@ -0,0 +1,31 @@ + + + + diff --git a/src/components/explore/InfoElement.vue b/src/components/explore/InfoElement.vue new file mode 100644 index 00000000..3610f7a4 --- /dev/null +++ b/src/components/explore/InfoElement.vue @@ -0,0 +1,45 @@ + + + diff --git a/src/components/explore/PairTable.vue b/src/components/explore/PairTable.vue new file mode 100644 index 00000000..102821d1 --- /dev/null +++ b/src/components/explore/PairTable.vue @@ -0,0 +1,76 @@ + + + diff --git a/src/components/explore/PriceHistoryGraph.vue b/src/components/explore/PriceHistoryGraph.vue new file mode 100644 index 00000000..d90f8212 --- /dev/null +++ b/src/components/explore/PriceHistoryGraph.vue @@ -0,0 +1,329 @@ + + + + + diff --git a/src/components/explore/StatElement.vue b/src/components/explore/StatElement.vue new file mode 100644 index 00000000..3fae220a --- /dev/null +++ b/src/components/explore/StatElement.vue @@ -0,0 +1,21 @@ + + diff --git a/src/components/explore/TableCell.vue b/src/components/explore/TableCell.vue new file mode 100644 index 00000000..b150322d --- /dev/null +++ b/src/components/explore/TableCell.vue @@ -0,0 +1,46 @@ + + + + diff --git a/src/components/explore/TokenTable.vue b/src/components/explore/TokenTable.vue new file mode 100644 index 00000000..e04b193e --- /dev/null +++ b/src/components/explore/TokenTable.vue @@ -0,0 +1,92 @@ + + + diff --git a/src/components/explore/TransactionTable.vue b/src/components/explore/TransactionTable.vue new file mode 100644 index 00000000..d1405c70 --- /dev/null +++ b/src/components/explore/TransactionTable.vue @@ -0,0 +1,117 @@ + + + diff --git a/src/lib/utils.js b/src/lib/utils.js index 548782ec..dd055695 100644 --- a/src/lib/utils.js +++ b/src/lib/utils.js @@ -2,6 +2,12 @@ import { formatAmount, AE_AMOUNT_FORMATS, decode } from '@aeternity/aepp-sdk'; import BigNumber from 'bignumber.js'; import dexContractsErrorMessages from 'dex-contracts-v2/build/errors'; import dexUiErrorMessages from '@/lib/errors'; +import { DEFAULT_NETWORKS } from '@/lib/constants'; + +// eslint-disable-next-line no-extend-native,func-names +BigInt.prototype.toJSON = function () { + return this.toString(); +}; const errorMessages = { ...dexContractsErrorMessages, @@ -188,3 +194,30 @@ export const handleCallError = ({ returnType, returnValue }, instance) => { */ export const isDexBackendDisabled = import.meta.env.VITE_DISABLE_DEX_BACKEND && JSON.parse(import.meta.env.VITE_DISABLE_DEX_BACKEND); + +export const shortenAddress = (address, lengthStart = 6, lengthEnd = 3) => + address ? `${address.slice(0, lengthStart)}...${address.slice(-lengthEnd)}` : ''; + +export const formatAmountPretty = (amount, decimals) => { + if (amount === null || new BigNumber(amount).isNaN()) return 'N/A'; + const formattedAmount = new BigNumber(amount).div(new BigNumber(10).pow(decimals)).abs(); + return formattedAmount + .toFixed(Math.max(0, 5 - formattedAmount.toFixed(0).length)) + .replace(/\.0*$/, '') // remove trailing .0 + .replace(/\B(? { + const formattedAmount = formatAmountPretty(amount, decimals); + return formattedAmount === 'N/A' ? formattedAmount : `$${formattedAmount}`; +}; + +export const detectAndModifyWAE = (token) => { + // find the wrapped ae token and modify it on any network + const waeAddresses = DEFAULT_NETWORKS.map((network) => network.waeAddress); + if (waeAddresses.includes(token.address)) { + return { ...token, symbol: 'AE', decimals: 18, name: 'Aeternity', isAe: true }; + } + return token; +}; diff --git a/src/locales/en.json b/src/locales/en.json index 60fa2e12..fd8d73b9 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -2,6 +2,7 @@ "nav": { "swap": "Swap", "pool": "Pool", + "explore": "Explore", "aboutUs": "About us", "aboutDex": "About DEX", "termsCondition": "Terms & Condition", @@ -165,6 +166,10 @@ "middlewareNotAvailable": "Middleware is not available, some functionality may not work properly.", "middlewareOutOfSync": "The middleware is out of sync, some functionality may not work properly." }, + "poolDetail": { + "swap": "Swap", + "addLiquidity": "Add Liquidity" + }, "superheroDex": "Superhero DEX", "initializing": "Initializing...", "connectWallet": "Connect Wallet", diff --git a/src/mixins/saveTokenSelectionMixin.js b/src/mixins/saveTokenSelectionMixin.js index 80972688..792f3316 100644 --- a/src/mixins/saveTokenSelectionMixin.js +++ b/src/mixins/saveTokenSelectionMixin.js @@ -72,8 +72,8 @@ export default { const getToken = (token) => this.$store.getters['tokens/getAvailableTokens']().find( (_token) => - (_token.is_ae ? _token.symbol : _token.contract_id) === token && - _token.networkId === this.$store.state.networkId, + (_token.is_ae && _token.symbol === token ? _token.symbol : _token.contract_id) === + token && _token.networkId === this.$store.state.networkId, ); if ((from && !getToken(from)) || (to && !getToken(to))) { await new Promise((resolve) => { diff --git a/src/router/index.js b/src/router/index.js index 8e5acdec..1594f7f5 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -1,11 +1,14 @@ import { h } from 'vue'; import { createRouter, createWebHistory, RouterView } from 'vue-router'; +import ExploreView from '../views/ExploreView.vue'; import SwapView from '../views/SwapView.vue'; import PoolView from '../views/PoolView.vue'; import ImportPool from '../views/ImportPool.vue'; import AddLiquidity from '../views/AddLiquidity.vue'; import RemoveLiquidity from '../views/RemoveLiquidity.vue'; import NotFound from '../views/NotFound.vue'; +import TokenDetailView from '../views/TokenDetailView.vue'; +import PoolDetailView from '../views/PoolDetailView.vue'; const routes = [ { @@ -43,6 +46,27 @@ const routes = [ }, ], }, + { + path: '/explore', + component: { render: () => h(RouterView) }, + children: [ + { + path: '', + name: 'explore', + component: ExploreView, + }, + { + path: 'tokens/:id', + name: 'token-detail', + component: TokenDetailView, + }, + { + path: 'pools/:id', + name: 'pool-detail', + component: PoolDetailView, + }, + ], + }, { path: '/404', name: 'not-found', diff --git a/src/store/index.js b/src/store/index.js index 0f5a7ea7..6e6503f0 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -1,22 +1,22 @@ import { shallowRef } from 'vue'; import { createStore } from 'vuex'; import { - Node, + AeSdk, AeSdkAepp, - walletDetector, BrowserWindowMessageConnection, - AeSdk, - unpackTx, + Node, RpcRejectedByUserError, + unpackTx, + walletDetector, } from '@aeternity/aepp-sdk'; import createPersistedState from 'vuex-persistedstate'; import { - handleUnknownError, - findErrorExplanation, createDeepLinkUrl, - resolveWithTimeout, - isSafariBrowser, + findErrorExplanation, + handleUnknownError, isDexBackendDisabled, + isSafariBrowser, + resolveWithTimeout, } from '@/lib/utils'; import { DEFAULT_NETWORKS, IN_FRAME, IS_MOBILE } from '@/lib/constants'; import aeternityModule from './modules/aeternity'; @@ -49,11 +49,8 @@ export default createStore({ }, // returns the network object for the currently selected network // or null if no network is selected - activeNetwork({ sdk }, { networks }) { - return ( - sdk && - Object.values(networks).find((network) => network.networkName === sdk.selectedNodeName) - ); + activeNetwork({ sdk, networkId }, { networks }) { + return sdk && Object.values(networks).find((network) => network.networkId === networkId); }, WAE({ networkId }, { activeNetwork }) { return networkId && activeNetwork ? activeNetwork.waeAddress : null; @@ -136,6 +133,10 @@ export default createStore({ onNetworkChange: ({ networkId }) => { dispatch('selectNetwork', networkId); }, + onAddressChange: ({ current }) => { + const [address] = Object.keys(current); + commit('setAddress', address); + }, name: 'DEX', onDisconnect() { commit('resetState'); @@ -220,12 +221,11 @@ export default createStore({ 'Login with your wallet has failed. Please make sure that you are logged into your wallet.', dismissText: 'Open My Wallet', resolve: () => { - const addressDeepLink = createDeepLinkUrl({ + window.location = createDeepLinkUrl({ type: 'address', 'x-success': `${window.location.href.split('?')[0]}?address={address}&networkId={networkId}`, 'x-cancel': window.location.href.split('?')[0], }); - window.location = addressDeepLink; }, }); } else { diff --git a/src/store/modules/dexBackend.js b/src/store/modules/dexBackend.js index 6e9b091f..0c590b96 100644 --- a/src/store/modules/dexBackend.js +++ b/src/store/modules/dexBackend.js @@ -118,10 +118,18 @@ export default { return up; }, - async fetchPairDetails({ getters: { getPairInfo }, dispatch }, { tokenA, tokenB }) { - const pair = getPairInfo({ tokenA, tokenB }); - if (!pair) return null; - const resp = await dispatch('safeFetch', { url: `pairs/by-address/${pair.address}` }); + async fetchPairDetails( + { getters: { getPairInfo }, dispatch }, + { pairAddress, tokenA, tokenB }, + ) { + let pair; + if (!pairAddress) { + pair = getPairInfo({ tokenA, tokenB }); + if (!pair) return null; + } + const resp = await dispatch('safeFetch', { + url: `pairs/by-address/${pairAddress || pair.address}`, + }); return ( resp && { ...resp, @@ -148,6 +156,10 @@ export default { })); }, + async getAllTokens({ dispatch }) { + return dispatch('safeFetch', { url: 'tokens' }); + }, + async fetchPairs({ dispatch, commit }, onlyListed) { const pairsXs = await dispatch('safeFetch', { url: `pairs?only-listed=${!!onlyListed}` }); if (!pairsXs) return null; @@ -158,5 +170,35 @@ export default { commit('setPairs', pairs); return pairs; }, + + async fetchHistory({ dispatch }, options) { + // fetch the full history with all pages + let history = []; + let offset = 0; + const limit = 9999999; + let page; + do { + const queryString = new URLSearchParams({ + limit, + offset, + ...options, + }).toString(); + // eslint-disable-next-line no-await-in-loop + page = await dispatch('safeFetch', { url: `history?${queryString}` }); + if (!page) break; + history = history.concat(page); + offset += limit; + } while (page.length === limit); + + return history; + }, + + async fetchPairsByToken({ dispatch }, tokenId) { + return dispatch('safeFetch', { url: `tokens/by-address/${tokenId}/pairs` }); + }, + + async fetchPairsByTokenUsd({ dispatch }, tokenId) { + return dispatch('safeFetch', { url: `pairs?token=${tokenId}` }); + }, }, }; diff --git a/src/store/modules/tokens.js b/src/store/modules/tokens.js index eedbb0c4..24bd8cb3 100644 --- a/src/store/modules/tokens.js +++ b/src/store/modules/tokens.js @@ -121,5 +121,9 @@ export default { } } }, + async fetchToken({ rootGetters: { activeNetwork } }, token) { + if (!activeNetwork) return null; + return fetchJson(`${activeNetwork.middlewareUrl}/v3/aex9/${token}`); + }, }, }; diff --git a/src/styles/globals.scss b/src/styles/globals.scss index 87d39fc4..9e58aed9 100644 --- a/src/styles/globals.scss +++ b/src/styles/globals.scss @@ -1,6 +1,9 @@ @use './variables.scss'; @use './mixins.scss'; @use './display-utilities.scss'; +@tailwind base; +@tailwind components; +@tailwind utilities; * { font-family: variables.$font-sans; @@ -19,6 +22,14 @@ body { background-color: variables.$color-black2; } +svg { + display: inline-block; +} + +img { + max-width: inherit; +} + @include mixins.laptop { #app { padding-bottom: 80px; diff --git a/src/views/ExploreView.vue b/src/views/ExploreView.vue new file mode 100644 index 00000000..fa30d11d --- /dev/null +++ b/src/views/ExploreView.vue @@ -0,0 +1,212 @@ + + + diff --git a/src/views/PoolDetailView.vue b/src/views/PoolDetailView.vue new file mode 100644 index 00000000..b254eb22 --- /dev/null +++ b/src/views/PoolDetailView.vue @@ -0,0 +1,314 @@ + + + diff --git a/src/views/PoolView.vue b/src/views/PoolView.vue index 540732f8..6b987213 100644 --- a/src/views/PoolView.vue +++ b/src/views/PoolView.vue @@ -22,7 +22,7 @@ {{ $t('pool.importIt') }} - + {{ address ? $t('addLiquidity.title') : $t('connectWallet') }} diff --git a/src/views/RemoveLiquidity.vue b/src/views/RemoveLiquidity.vue index 90f74288..9e7a39bd 100644 --- a/src/views/RemoveLiquidity.vue +++ b/src/views/RemoveLiquidity.vue @@ -135,6 +135,7 @@ class="remove-btn" :class="{ transparent: !enoughAllowance }" :disabled="!removeButtonEnabled" + data-cy="remove-liquidity-btn" @click="handleRemove" > {{ removing ? $t('removeLiquidity.removing') : $t('liquidityDetails.remove') }} @@ -266,7 +267,7 @@ export default { watch: { async address(newVal) { if (newVal && this.pairId) { - await this.refreshAllowance(this.pairId, this.fetchAlowance); + await this.refreshAllowance(this.pairId, this.fetchAllowance); } }, }, @@ -277,7 +278,7 @@ export default { this.tokenB = this.getAvailableTokens().find((t) => t.contract_id === tokenBContract); await this.setPairInfo(); if (this.pairId) { - await this.refreshAllowance(this.pairId, this.fetchAlowance); + await this.refreshAllowance(this.pairId, this.fetchAllowance); } }, methods: { @@ -292,7 +293,7 @@ export default { tokenBSymbol: this.tokenB.symbol, }); }, - fetchAlowance() { + fetchAllowance() { return this.$store.dispatch('aeternity/getRouterPairAllowance', { tokenA: this.tokenA.contract_id, tokenB: this.tokenB.contract_id, @@ -306,7 +307,7 @@ export default { tokenB: this.tokenB, amount: expandDecimals(this.poolTokenInput, 18), }); - await this.safeRefreshAllowance(this.pairId, this.poolTokenInput, 18, this.fetchAlowance); + await this.safeRefreshAllowance(this.pairId, this.poolTokenInput, 18, this.fetchAllowance); } catch (e) { this.approved = false; await this.$store.dispatch('showUnknownError', e); @@ -370,7 +371,7 @@ export default { await this.$store.dispatch('showUnknownError', e); } finally { await this.setPairInfo(); - await this.refreshAllowance(this.pairId, this.fetchAlowance); + await this.refreshAllowance(this.pairId, this.fetchAllowance); this.updatePercent(0); this.removing = false; } diff --git a/src/views/SwapView.vue b/src/views/SwapView.vue index 47fe86c6..3e58bbab 100644 --- a/src/views/SwapView.vue +++ b/src/views/SwapView.vue @@ -44,7 +44,7 @@ @click="approve" >
- + {{ approveBtnMessage }}
@@ -87,9 +87,11 @@ import AnimatedSpinner from '@/assets/animated-spinner.svg'; import saveTokenSelectionMixin from '@/mixins/saveTokenSelectionMixin'; import approvalMixin from '@/mixins/allowanceMixin'; import setSwapRoutesMixin from '@/mixins/setSwapRoutesMixin'; +import AddressAvatar from '@/components/AddressAvatar.vue'; export default { components: { + AddressAvatar, MainWrapper, InputToken, ButtonPlain, @@ -314,6 +316,10 @@ export default { background-color: variables.$color-black2; border: 4px solid variables.$color-black3; + svg { + display: block; + } + &:hover { opacity: 0.8; } diff --git a/src/views/TokenDetailView.vue b/src/views/TokenDetailView.vue new file mode 100644 index 00000000..16258bd3 --- /dev/null +++ b/src/views/TokenDetailView.vue @@ -0,0 +1,364 @@ + + + diff --git a/tailwind.config.js b/tailwind.config.js new file mode 100644 index 00000000..2a744f76 --- /dev/null +++ b/tailwind.config.js @@ -0,0 +1,8 @@ +/** @type {import('tailwindcss').Config} */ +export default { + content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'], + theme: { + extend: {}, + }, + plugins: [], +}; diff --git a/tests/e2e/specs/login.cy.js b/tests/e2e/specs/login.cy.js new file mode 100644 index 00000000..df6cc100 --- /dev/null +++ b/tests/e2e/specs/login.cy.js @@ -0,0 +1,42 @@ +describe('Login', () => { + it('testnet login', () => { + cy.visit('/') + .contains('.title', 'Swap') + .get('[data-cy=connect-wallet]') + .click() + .get('[data-cy=connect-Superhero]', { timeout: 10000 }) + .click() + .get('.onboarding-modal', { timeout: 10000 }) + .should('be.visible') + // close the popup + .get('.close') + .click() + .get('.onboarding-modal') + .should('not.exist') + // check that Testnet is written in the network selector + .get('.active-network') + .should('contain', 'Testnet'); + }); + + it('testnet login command', () => { + cy.login(); + }); + + it('mainnet login', () => { + cy.visit( + '/swap?address=ak_rRVV9aDnmmLriPePDSvfTUvepZtR2rbYk2Mx4GCqGLcc1DMAq&networkId=ae_mainnet', + ) + .contains('.title', 'Swap') + // popup should open with "Thanks for trying out the DEX!" + .get('.onboarding-modal', { timeout: 10000 }) + .should('be.visible') + // close the popup + .get('.close') + .click() + .get('.onboarding-modal') + .should('not.exist') + // check that mainnet is written in the network selector + .get('.active-network') + .should('contain', 'Mainnet'); + }); +}); diff --git a/tests/e2e/specs/pool.cy.js b/tests/e2e/specs/pool.cy.js new file mode 100644 index 00000000..18e36584 --- /dev/null +++ b/tests/e2e/specs/pool.cy.js @@ -0,0 +1,65 @@ +// TODO navigate to pool view +// TODO provide liquidity +// TODO deal with provide liquidity callback +// TODO remove liquidity +// TODO deal with remove liquidity callback + +describe('Pool', () => { + it('Pool View', () => { + cy.login() + .get('[data-cy=pool]') + .filter(':visible') + .click() + .get('.title') + .should('contain', 'Pool') + .get('.pool-view') + .should('be.visible'); + }); + + it('Provide Liquidity', () => { + cy.login(); + cy.get('[data-cy=pool]').filter(':visible').click(); + cy.get('.title').should('contain', 'Pool'); + cy.get('.pool-view').should('be.visible'); + + cy.get('[data-cy=add-liquidity]').click(); + cy.get('.add-liquidity').should('be.visible'); + cy.selectToken(0); + cy.selectToken(1, 'ct_b7FZHQzBcAW4r43ECWpV3qQJMQJp5BxkZUGNKrqqLyjVRN3SC'); + cy.get('.input-token input').eq(0).type('0.1'); + + cy.approveTokenUsageIfNessesary(); + + cy.interceptTxPost(); + cy.contains('Supply').click(); + cy.get('.transaction-details').should('be.visible'); + cy.contains('Confirm Supply').click(); + cy.wait('@walletSignTx', { timeout: 10000 }); + cy.wait('@postTx', { timeout: 10000 }); + + cy.get('.notification-transaction-status').should('be.visible'); + }); + + it('Import Pool Share', () => { + cy.importLiquidity(); + }); + + it('Remove Liquidity', () => { + // Setup test + cy.importLiquidity(); + cy.get('.liquidity-item').click(); + cy.get('.liquidity-item .body').should('be.visible'); + cy.contains('Remove').click(); + cy.get('.remove-liquidity').should('be.visible'); + cy.contains('100%').click(); + cy.approveTokenUsageIfNessesary(); + cy.interceptTxPost(); + cy.get('[data-cy="remove-liquidity-btn"]').click(); + cy.get('.confirm-add-modal').should('be.visible'); + cy.get('.confirm-add-modal button.primary').click(); + cy.wait('@walletSignTx', { timeout: 10000 }); + cy.wait('@postTx', { timeout: 10000 }); + + cy.get('.notification-transaction-status').should('be.visible'); + }); +}); diff --git a/tests/e2e/specs/swap.cy.js b/tests/e2e/specs/swap.cy.js index 52834397..8a5b9025 100644 --- a/tests/e2e/specs/swap.cy.js +++ b/tests/e2e/specs/swap.cy.js @@ -1,11 +1,47 @@ -// https://docs.cypress.io/api/introduction/api.html - -describe('My First Test', () => { - it.skip('Visits the app root url', () => { - cy.visit('/') - .contains('.title', 'Swap') - .login() - .wait(5000) // TODO: remove when adding swap tests - .logout(); +describe('Swap', () => { + it('Swap Token', () => { + cy.interceptTxPost(); + cy.login(); + cy.selectToken(0); + cy.selectToken(1, 'ct_b7FZHQzBcAW4r43ECWpV3qQJMQJp5BxkZUGNKrqqLyjVRN3SC'); + + cy.get('.input-token').first().should('contain', 'AE'); + + // expect ae to be selected + cy.get('.input-token').eq(1).should('contain', 'TAEX9-A'); + // input amount + cy.get('.input-token input') + .eq(0) + .type('1') + // expect second input to have a number + .get('.input-token input') + .eq(1) + .invoke('val') + .should((value) => { + expect(Number.isNaN(+value)).to.eq(false); // passes + }) + .get('main button.primary') + .click() + // confirm modal should open + .get('.confirm-swap-modal') + .should('be.visible') + // confirm transaction + .get('.confirm-swap-modal button.primary') + .click(); + // expect wallet to be opened + cy.wait('@walletSignTx', { timeout: 10000 }); + cy.wait('@postTx', { timeout: 10000 }); + + cy.get('.notification-transaction-status').should('be.visible'); + }); + + it('Adjust Swap Settings', () => { + cy.login(); + cy.get('.swap-view .actions-menu').click(); + cy.get('.settings').should('be.visible'); + cy.get('.settings .input-amount input').eq(0).type('8'); + cy.get('.settings .input-amount input').eq(1).type('8'); + cy.get('.overlay').click({ force: true }); + cy.get('.settings').should('not.exist'); }); }); diff --git a/tests/e2e/support/commands.js b/tests/e2e/support/commands.js index 6d8e543f..339b5feb 100644 --- a/tests/e2e/support/commands.js +++ b/tests/e2e/support/commands.js @@ -1,52 +1,91 @@ -// *********************************************** -// This example commands.js shows you how to -// create various custom commands and overwrite -// existing commands. -// -// For more comprehensive examples of custom -// commands please read more here: -// https://on.cypress.io/custom-commands -// *********************************************** -// -// -// -- This is a parent command -- +import aeSdk from './testAeSdk'; + Cypress.Commands.add('login', () => { - cy.get('[data-cy=connect-wallet]') - .click() - .get('[data-cy=connect-Superhero]', { timeout: 60000 }) + cy.visit(`/swap?address=${aeSdk.address}&networkId=ae_uat`) + .contains('.title', 'Swap') + // popup should open with "Thanks for trying out the DEX!" + .get('.onboarding-modal', { timeout: 10000 }) + .should('be.visible') + // close the popup + .get('.close') .click() - .get('[data-cy=connect-Superhero]', { timeout: 60000 }) + .get('.onboarding-modal') .should('not.exist') - .get('body') - .then((body) => { - if (body.find('[data-cy=wallet-address]').length > 0) { - cy.get('[data-cy=wallet-address]').should('contain', 'ak_'); - } else { - cy.get('[data-cy=error-dismiss]', { timeout: 6000 }) - .click() - .get('[data-cy=checkbox]') - .click() - .get('[data-cy=import-wallet]') - .click() - .get('[data-cy=textarea]') - .type('grief huge rare weather proof clerk pilot edit speak network denial debris') - .get('[data-cy=import]') - .click() - .get('button') - .contains('Confirm') - .click() - .visit('/') - .login(); - } - }); + // check that testnet is written in the network selector + .get('.active-network') + .should('contain', 'Testnet'); }); -Cypress.Commands.add('logout', () => { - cy.get('[data-cy=wallet-address]').click().get('[data-cy=wallet-disconnect]').click(); - // TODO: check if we gonna disconnect from wallet.superhero.com too +Cypress.Commands.add('selectToken', (slot, token, initialSelector = '.input-token button') => { + // get first .input-token + cy.get(initialSelector) + .eq(slot) + .click() + // token pop up should open + .get('.select-token-modal') + .should('be.visible'); + // select first token + if (token) { + cy.get('.select-token-modal .search-bar') + .type(token) + .get('.select-token-modal .import-button') + .click() + // warning should be shown + .get('.select-token-modal') + .should('contain', 'Make sure this is the token that you want to trade.') + // confirm import + .get('.select-token-modal .import-button') + .click(); + } else { + cy.get('.select-token-modal .token').click(); + } }); -Cypress.Commands.add('switchTestnetNetwork', () => { - cy.wait(1000); - // TODO +Cypress.Commands.add('interceptTxPost', () => { + cy.intercept( + { + method: 'POST', + url: 'https://testnet.aeternity.io/v3/transactions*', + times: 1, + }, + { tx_hash: 'th_8zREhgdJmg8LxG5hnJ2Eq63n7ZTbJMeZfi8EETDjtdnmv4Ksk' }, + ).as('postTx'); +}); + +Cypress.Commands.add('importLiquidity', () => { + cy.login(); + cy.get('[data-cy=pool]').filter(':visible').click(); + cy.get('.title').should('contain', 'Pool'); + cy.get('.pool-view').should('be.visible'); + + cy.contains('Import it.').click(); + + cy.get('.import-pool').should('be.visible'); + cy.selectToken(0, undefined, '.button-token'); + cy.selectToken(1, 'ct_b7FZHQzBcAW4r43ECWpV3qQJMQJp5BxkZUGNKrqqLyjVRN3SC', '.button-token'); + + cy.get('.pool-found').should('be.visible'); + + cy.contains('Ok').click(); + + cy.get('.liquidity-item').should('be.visible'); +}); + +Cypress.Commands.add('approveTokenUsageIfNessesary', () => { + cy.contains('Approve').then(($btn) => { + if (!$btn.is(':disabled')) { + // intercept the approval transaction + cy.intercept({ + method: 'POST', + url: 'https://testnet.aeternity.io/v3/transactions*', + times: 1, + }).as('postTx'); + cy.contains('Approve').click(); + cy.wait('@walletSignTx', { timeout: 10000 }); + cy.wait('@postTx', { timeout: 10000 }); + + // wait for notification to appear + cy.get('.notification-transaction-status').should('be.visible', { timeout: 10000 }); + } + }); }); diff --git a/tests/e2e/support/index.js b/tests/e2e/support/index.js index 37a498fb..ae8fa475 100644 --- a/tests/e2e/support/index.js +++ b/tests/e2e/support/index.js @@ -15,6 +15,4 @@ // Import commands.js using ES2015 syntax: import './commands'; - -// Alternatively you can use CommonJS syntax: -// require('./commands') +import './networkStubs'; diff --git a/tests/e2e/support/networkStubs.js b/tests/e2e/support/networkStubs.js new file mode 100644 index 00000000..86008419 --- /dev/null +++ b/tests/e2e/support/networkStubs.js @@ -0,0 +1,26 @@ +import aeSdk from './testAeSdk'; + +beforeEach(() => { + cy.intercept('GET', 'https://wallet.superhero.com/sign-transaction*', (req) => { + // extract transaction from query + const transaction = new URL(req.url).searchParams.get('transaction'); + const callback = new URL(req.url).searchParams.get('x-success'); + + return aeSdk.signTransaction(transaction).then((signedTx) => { + req.redirect(callback.replace('{transaction}', encodeURIComponent(signedTx))); + }); + }).as('walletSignTx'); + + cy.intercept('GET', 'https://wallet.superhero.com/address*', (req) => { + // extract transaction from query + const callback = new URL(req.url).searchParams.get('x-success'); + + return aeSdk.getNodeInfo().then((info) => { + req.redirect( + callback.replace('{address}', aeSdk.address).replace('{networkId}', info.nodeNetworkId), + ); + }); + }).as('walletAddress'); +}); + +export {}; diff --git a/tests/e2e/support/testAeSdk.js b/tests/e2e/support/testAeSdk.js new file mode 100644 index 00000000..c876e594 --- /dev/null +++ b/tests/e2e/support/testAeSdk.js @@ -0,0 +1,14 @@ +import { AeSdk, Node, MemoryAccount } from '@aeternity/aepp-sdk'; + +const PAYER_ACCOUNT_SECRET_KEY = + '9ebd7beda0c79af72a42ece3821a56eff16359b6df376cf049aee995565f022f840c974b97164776454ba119d84edc4d6058a8dec92b6edc578ab2d30b4c4200'; +const NODE_URL = 'https://testnet.aeternity.io'; + +const payerAccount = new MemoryAccount(PAYER_ACCOUNT_SECRET_KEY); +const node = new Node(NODE_URL); +const aeSdk = new AeSdk({ + nodes: [{ name: 'testnet', instance: node }], + accounts: [payerAccount], +}); + +export default aeSdk;