From 6d2d4d083bb69d6594fddd34bab2f570a5599e91 Mon Sep 17 00:00:00 2001 From: gmolki Date: Tue, 26 Nov 2024 10:03:26 +0100 Subject: [PATCH 1/9] deps: upgrade front-core to 1.24.2 --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 105f26a..7720e78 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,7 @@ "name": "front-aleph-account-page", "version": "0.10.4", "dependencies": { - "@aleph-front/core": "^1.22.4", + "@aleph-front/core": "^1.24.2", "@aleph-sdk/account": "^1.0.3", "@aleph-sdk/avalanche": "^1.0.3", "@aleph-sdk/client": "^1.0.6", @@ -57,9 +57,9 @@ } }, "node_modules/@aleph-front/core": { - "version": "1.22.4", - "resolved": "https://registry.npmjs.org/@aleph-front/core/-/core-1.22.4.tgz", - "integrity": "sha512-5UXx4rEvwDWygIyNPP6yRevrrlF7ZMigCB24AzFZ4ezyWn999zi/I7TReUgIkTpLGy4/s2Xhli7OrxIaGVIjhw==", + "version": "1.25.0", + "resolved": "https://registry.npmjs.org/@aleph-front/core/-/core-1.25.0.tgz", + "integrity": "sha512-Q4PWBrWu/iW9aDXDKnX3y77PuUK4uhPSRY23sO8C7hXWjfdtE2YraToft5Gfrh5Dv/Ngkldb8pxdidKqQD70Yw==", "dependencies": { "@monaco-editor/react": "^4.4.6", "react-infinite-scroll-hook": "^4.1.1", diff --git a/package.json b/package.json index 3256790..7f898f5 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "lint:fix": "next lint --fix" }, "dependencies": { - "@aleph-front/core": "^1.22.4", + "@aleph-front/core": "^1.24.2", "@aleph-sdk/account": "^1.0.3", "@aleph-sdk/avalanche": "^1.0.3", "@aleph-sdk/client": "^1.0.6", From 5ac9a3459ea251a067476c63441594beef27d979 Mon Sep 17 00:00:00 2001 From: gmolki Date: Wed, 4 Dec 2024 09:57:42 +0100 Subject: [PATCH 2/9] feat: create ExternalLink cmp --- src/components/common/ExternalLink/cmp.tsx | 30 ++++++++++++++++++++ src/components/common/ExternalLink/index.ts | 2 ++ src/components/common/ExternalLink/styles.ts | 17 +++++++++++ src/components/common/ExternalLink/types.ts | 9 ++++++ 4 files changed, 58 insertions(+) create mode 100644 src/components/common/ExternalLink/cmp.tsx create mode 100644 src/components/common/ExternalLink/index.ts create mode 100644 src/components/common/ExternalLink/styles.ts create mode 100644 src/components/common/ExternalLink/types.ts diff --git a/src/components/common/ExternalLink/cmp.tsx b/src/components/common/ExternalLink/cmp.tsx new file mode 100644 index 0000000..293162e --- /dev/null +++ b/src/components/common/ExternalLink/cmp.tsx @@ -0,0 +1,30 @@ +import { memo } from 'react' +import { StyledExternalLink } from './styles' +import { ExternalLinkProps } from './types' +import { Icon } from '@aleph-front/core' + +export const ExternalLink = ({ + text, + href, + color, + typo, + underline, +}: ExternalLinkProps) => { + return ( + <> + + {text ? text : href} + + + + ) +} +ExternalLink.displayName = 'ExternalLink' + +export default memo(ExternalLink) as typeof ExternalLink diff --git a/src/components/common/ExternalLink/index.ts b/src/components/common/ExternalLink/index.ts new file mode 100644 index 0000000..5047618 --- /dev/null +++ b/src/components/common/ExternalLink/index.ts @@ -0,0 +1,2 @@ +export { default } from './cmp' +export type { ExternalLinkProps } from './types' diff --git a/src/components/common/ExternalLink/styles.ts b/src/components/common/ExternalLink/styles.ts new file mode 100644 index 0000000..3ee2786 --- /dev/null +++ b/src/components/common/ExternalLink/styles.ts @@ -0,0 +1,17 @@ +import { CoreTheme, getTypoCss } from '@aleph-front/core' +import styled from 'styled-components' +import { css } from 'styled-components' + +export type StyledExternalLinkProps = { + $color?: keyof CoreTheme['color'] + $typo?: keyof CoreTheme['typo'] + $underline?: boolean +} + +export const StyledExternalLink = styled.a` + ${({ theme, $color = 'white', $typo, $underline = false }) => css` + color: ${theme.color[$color]}; + text-decoration: ${$underline ? 'underline' : 'none'}; + ${$typo ? getTypoCss($typo) : ''} + `} +` diff --git a/src/components/common/ExternalLink/types.ts b/src/components/common/ExternalLink/types.ts new file mode 100644 index 0000000..c0cd7a0 --- /dev/null +++ b/src/components/common/ExternalLink/types.ts @@ -0,0 +1,9 @@ +import { CoreTheme } from '@aleph-front/core' + +export type ExternalLinkProps = { + href: string + text?: string + color?: keyof CoreTheme['color'] + typo?: keyof CoreTheme['typo'] + underline?: boolean +} From 1da904a4cb2d34ff8c3cefe86945165562680b41 Mon Sep 17 00:00:00 2001 From: gmolki Date: Tue, 10 Dec 2024 21:35:09 +0100 Subject: [PATCH 3/9] deps: upgrade sdk --- package-lock.json | 152 ++++++++++++++++++++++--------------- package.json | 18 ++--- src/domain/connect/base.ts | 27 +++---- src/domain/file.ts | 2 +- src/domain/node.ts | 4 +- src/domain/stake.ts | 4 +- src/helpers/utils.ts | 26 +------ 7 files changed, 118 insertions(+), 115 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7720e78..dc86ecc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,15 +9,15 @@ "version": "0.10.4", "dependencies": { "@aleph-front/core": "^1.24.2", - "@aleph-sdk/account": "^1.0.3", - "@aleph-sdk/avalanche": "^1.0.3", - "@aleph-sdk/client": "^1.0.6", - "@aleph-sdk/core": "^1.0.3", - "@aleph-sdk/ethereum": "^1.0.3", - "@aleph-sdk/evm": "^1.0.3", - "@aleph-sdk/message": "^1.0.6", - "@aleph-sdk/solana": "^1.0.3", - "@aleph-sdk/superfluid": "^1.0.6", + "@aleph-sdk/account": "^1.2.0", + "@aleph-sdk/avalanche": "^1.2.0", + "@aleph-sdk/client": "^1.2.4", + "@aleph-sdk/core": "^1.2.0", + "@aleph-sdk/ethereum": "^1.2.0", + "@aleph-sdk/evm": "^1.2.0", + "@aleph-sdk/message": "^1.3.0", + "@aleph-sdk/solana": "^1.2.1", + "@aleph-sdk/superfluid": "^1.2.0", "@fortawesome/fontawesome-svg-core": "^6.3.0", "@hookform/resolvers": "^3.1.1", "@types/node": "18.14.1", @@ -56,6 +56,37 @@ "typescript": "^5.4.5" } }, + "../aleph-sdk-ts/packages/client": { + "name": "@aleph-sdk/client", + "version": "1.2.4", + "license": "MIT", + "devDependencies": { + "@types/sha.js": "^2.4.4" + }, + "peerDependencies": { + "@aleph-sdk/account": "^1.x.x", + "@aleph-sdk/core": "^1.x.x", + "@aleph-sdk/message": "^1.x.x" + } + }, + "../aleph-sdk-ts/packages/message": { + "name": "@aleph-sdk/message", + "version": "1.3.0", + "license": "MIT", + "dependencies": { + "axios": "^1.5.1", + "form-data": "^4.0.0", + "sha.js": "^2.4.11" + }, + "devDependencies": { + "@types/sha.js": "^2.4.4" + }, + "peerDependencies": { + "@aleph-sdk/account": "^1.x.x", + "@aleph-sdk/core": "^1.x.x", + "@aleph-sdk/evm": "^1.x.x" + } + }, "node_modules/@aleph-front/core": { "version": "1.25.0", "resolved": "https://registry.npmjs.org/@aleph-front/core/-/core-1.25.0.tgz", @@ -79,17 +110,17 @@ } }, "node_modules/@aleph-sdk/account": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@aleph-sdk/account/-/account-1.0.3.tgz", - "integrity": "sha512-LVeY3bJ5pg1JxQpnIB7f1Dz4TVN1SuJeI4dFKJy/71r6foF1zUxzWt+dbedc1zhOspjlgf5oU6gsXZrkzUGLhQ==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@aleph-sdk/account/-/account-1.2.0.tgz", + "integrity": "sha512-JjVQFYm+/tfddJXjYbup6FZf8Lsn7VtNNandOV9L2+0x7RWtRydrgDb2UuFE4JaJS7UYQTzLmgAFL6LqVoQNsA==", "peerDependencies": { "@aleph-sdk/core": "^1.x.x" } }, "node_modules/@aleph-sdk/avalanche": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@aleph-sdk/avalanche/-/avalanche-1.0.3.tgz", - "integrity": "sha512-GscbaEm34Ps2nDqqvfYir6O57PDpbkNvan2v4RXuy6Eb3ZzgkoOQumYRw+biDGI97cvQmin26xEFMfkPX6TBlw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@aleph-sdk/avalanche/-/avalanche-1.2.0.tgz", + "integrity": "sha512-ZKt/lsoMHoYroK9xb8p2QnnPEu7LyJ+eSvmXz2FrE3N4+7zgIsj2OvViM90Y/2rQPB5gM90zx1sM/Oqtpspt+A==", "dependencies": { "eciesjs": "^0.4.6", "sha.js": "^2.4.11" @@ -102,25 +133,37 @@ "ethers": "^5.x.x" } }, - "node_modules/@aleph-sdk/client": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@aleph-sdk/client/-/client-1.0.6.tgz", - "integrity": "sha512-wYqzDy0uYrZrJL2CyScZOR8UfjGtfBGkZkuc4cwLSkwNXtvp2zt+OL8G4jZGMHUNn4M7dEKHKQn/C51nhGfvMw==", + "node_modules/@aleph-sdk/base": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@aleph-sdk/base/-/base-1.2.0.tgz", + "integrity": "sha512-qm2LPi1KilAlVSCvg96itYBt3FyMgLQ25C6ZMQNceyTZBr8p1v3MX24YdjM5fjIdLaRnT7TPSwyNskNgHJeh8g==", + "peer": true, + "dependencies": { + "eciesjs": "^0.4.6", + "sha.js": "^2.4.11" + }, "peerDependencies": { "@aleph-sdk/account": "^1.x.x", "@aleph-sdk/core": "^1.x.x", - "@aleph-sdk/message": "^1.x.x" + "@aleph-sdk/ethereum": "^1.x.x", + "@aleph-sdk/evm": "^1.x.x", + "avalanche": "^3.15.3", + "ethers": "^5.x.x" } }, + "node_modules/@aleph-sdk/client": { + "resolved": "../aleph-sdk-ts/packages/client", + "link": true + }, "node_modules/@aleph-sdk/core": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@aleph-sdk/core/-/core-1.0.3.tgz", - "integrity": "sha512-+enhpR4U7jcEvKDRPuaQo2+Qxw2L0ZEDGLtDsKiwpBqYaNHkreCtTpD0gZy+eNulpFYLifv5q8FE6m+Hvvc9OA==" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@aleph-sdk/core/-/core-1.2.0.tgz", + "integrity": "sha512-DF4IE15c/yzq1q1l7IA+mfY1VkeIWLQfxAjUPxfJnjtjobIMhGAgjhIRFJu6vbvkSetmShgmGeN/r1D8wHKhrQ==" }, "node_modules/@aleph-sdk/ethereum": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@aleph-sdk/ethereum/-/ethereum-1.0.3.tgz", - "integrity": "sha512-hxpLUpogRawyOXVTBXq0tHh/jQLLB/9FC6sMuuxGSiN5sYHIwimM8tTDkroAFTcrNVx7m6veagcIVSvm/YfERA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@aleph-sdk/ethereum/-/ethereum-1.2.0.tgz", + "integrity": "sha512-P/UmEyegEMOgYAqlj59L0ce3wXzMl9DmrFKcr8WT/wxbg4mwrH77lk60mUsgxY0Vx00rZNOBWgZ862uaQXo55Q==", "dependencies": { "bip39": "^3.1.0", "eciesjs": "^0.4.6" @@ -133,9 +176,9 @@ } }, "node_modules/@aleph-sdk/evm": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@aleph-sdk/evm/-/evm-1.0.3.tgz", - "integrity": "sha512-JaCsuRszxrZy+PGTE9svXst1+RkCbiAIYHtA5HNwD+Ow35NixjqxaxcqlK4UnRSYvIs/2y16YuN8+69jnSg+qw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@aleph-sdk/evm/-/evm-1.2.0.tgz", + "integrity": "sha512-DA1HCcwtsr2S7GnNI2+zAAgTHtts3V5GOCz8eoJW9EPsGBN0P2K1AZ2XrMrkjJYcg568aeKY9717ayN6cAiu3g==", "dependencies": { "@metamask/eth-sig-util": "^7.0.1", "ethereumjs-util": "^7.1.5" @@ -147,23 +190,13 @@ } }, "node_modules/@aleph-sdk/message": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@aleph-sdk/message/-/message-1.0.6.tgz", - "integrity": "sha512-Hzv+uBN0aaYIaavZqwT8WT04+Y0WJO/uApqNmyqaSq7ZY39eiLZt+s96HeTnOSevFCUKInEETVQtdlha/TsTOA==", - "dependencies": { - "axios": "^1.5.1", - "form-data": "^4.0.0", - "sha.js": "^2.4.11" - }, - "peerDependencies": { - "@aleph-sdk/account": "^1.x.x", - "@aleph-sdk/core": "^1.x.x" - } + "resolved": "../aleph-sdk-ts/packages/message", + "link": true }, "node_modules/@aleph-sdk/solana": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@aleph-sdk/solana/-/solana-1.0.3.tgz", - "integrity": "sha512-dU05Hzt8g5f7foWQaur35KTwFelgA7pYsACADbn7X+EH8g0dbL6wPse+we314zhnPMxMtkOarDB5sbJenB9yPg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@aleph-sdk/solana/-/solana-1.2.1.tgz", + "integrity": "sha512-lvCu6nAOCj35Q4eLIjZl66v3gDEpbb/VTvHLul3SlcMIRnnyIUyesD/mP8sL8zW21sB8152g1iHkPitgDx6abg==", "dependencies": { "bs58": "^5.0.0", "tweetnacl": "^1.0.3" @@ -189,12 +222,13 @@ } }, "node_modules/@aleph-sdk/superfluid": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@aleph-sdk/superfluid/-/superfluid-1.0.6.tgz", - "integrity": "sha512-eScDFCxUyswrOnXulR42CctIVyGGgov/cOCFxsqjcsGCXyHRRwOp/kyhH2rEuySJUene9xXvtoaBHphObd1rCg==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@aleph-sdk/superfluid/-/superfluid-1.2.0.tgz", + "integrity": "sha512-amXZrY7ZdeUHE2us3KiFLSDcNoimg9I1JMC6GDYpM2zRKGNQLJwYQp+cXUGwSTh4rFykRXg/Wdfvo/MAw3usQg==", "peerDependencies": { "@aleph-sdk/account": "^1.x.x", "@aleph-sdk/avalanche": "^1.x.x", + "@aleph-sdk/base": "^1.x.x", "@aleph-sdk/core": "^1.x.x", "@aleph-sdk/evm": "^1.x.x", "@superfluid-finance/sdk-core": "^0.6.12", @@ -6224,7 +6258,8 @@ "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "peer": true }, "node_modules/atomic-sleep": { "version": "1.0.0", @@ -6340,16 +6375,6 @@ "node": ">=4" } }, - "node_modules/axios": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz", - "integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==", - "dependencies": { - "follow-redirects": "^1.15.0", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, "node_modules/axobject-query": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.1.1.tgz", @@ -7701,6 +7726,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "peer": true, "dependencies": { "delayed-stream": "~1.0.0" }, @@ -8480,6 +8506,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "peer": true, "engines": { "node": ">=0.4.0" } @@ -10368,6 +10395,7 @@ "url": "https://github.com/sponsors/RubenVerborgh" } ], + "peer": true, "engines": { "node": ">=4.0" }, @@ -10398,6 +10426,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "peer": true, "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -12522,6 +12551,7 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "peer": true, "engines": { "node": ">= 0.6" } @@ -12530,6 +12560,7 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "peer": true, "dependencies": { "mime-db": "1.52.0" }, @@ -13983,11 +14014,6 @@ "resolved": "https://registry.npmjs.org/proxy-compare/-/proxy-compare-2.5.1.tgz", "integrity": "sha512-oyfc0Tx87Cpwva5ZXezSp5V9vht1c7dZBhvuV/y3ctkgMVUmiAGDVeeB0dKhGSyT0v1ZTEQYpe/RXlBVBNuCLA==" }, - "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==" - }, "node_modules/psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", diff --git a/package.json b/package.json index 7f898f5..e40045f 100644 --- a/package.json +++ b/package.json @@ -12,15 +12,15 @@ }, "dependencies": { "@aleph-front/core": "^1.24.2", - "@aleph-sdk/account": "^1.0.3", - "@aleph-sdk/avalanche": "^1.0.3", - "@aleph-sdk/client": "^1.0.6", - "@aleph-sdk/core": "^1.0.3", - "@aleph-sdk/ethereum": "^1.0.3", - "@aleph-sdk/evm": "^1.0.3", - "@aleph-sdk/message": "^1.0.6", - "@aleph-sdk/solana": "^1.0.3", - "@aleph-sdk/superfluid": "^1.0.6", + "@aleph-sdk/account": "^1.2.0", + "@aleph-sdk/avalanche": "^1.2.0", + "@aleph-sdk/client": "^1.2.4", + "@aleph-sdk/core": "^1.2.0", + "@aleph-sdk/ethereum": "^1.2.0", + "@aleph-sdk/evm": "^1.2.0", + "@aleph-sdk/message": "^1.3.0", + "@aleph-sdk/solana": "^1.2.1", + "@aleph-sdk/superfluid": "^1.2.0", "@fortawesome/fontawesome-svg-core": "^6.3.0", "@hookform/resolvers": "^3.1.1", "@types/node": "18.14.1", diff --git a/src/domain/connect/base.ts b/src/domain/connect/base.ts index e71b4d4..60b2f92 100644 --- a/src/domain/connect/base.ts +++ b/src/domain/connect/base.ts @@ -9,18 +9,19 @@ import { getAccountFromProvider as getSOLAccount, SOLAccount, } from '@aleph-sdk/solana' +import { getAccountFromProvider as getAVAXAccount } from '@aleph-sdk/avalanche' import { - getAccountFromProvider as getAVAXAccount, - AvalancheAccount, -} from '@aleph-sdk/avalanche' -import { createFromAvalancheAccount } from '@aleph-sdk/superfluid' + createFromEVMAccount, + isAccountSupported as isAccountPAYGCompatible, +} from '@aleph-sdk/superfluid' import Err from '../../helpers/errors' -import { Mutex, getERC20Balance, getSOLBalance, sleep } from '@/helpers/utils' +import { Mutex, getAddressBalance, sleep } from '@/helpers/utils' import { MetaMaskInpageProvider } from '@metamask/providers' import type { Provider as EthersProvider, CombinedProvider, } from '@web3modal/scaffold-utils/ethers' +import { EVMAccount } from '@aleph-sdk/evm' export { BlockchainId } @@ -249,12 +250,11 @@ export abstract class BaseConnectionProviderManager { } async getBalance(account: Account): Promise { - if (account instanceof AvalancheAccount) { + if (isAccountPAYGCompatible(account)) { try { - // @note: refactor in SDK calling init inside this method - const superfluidAccount = createFromAvalancheAccount(account) - await superfluidAccount.init() - + const superfluidAccount = await createFromEVMAccount( + account as EVMAccount, + ) const balance = await superfluidAccount.getALEPHBalance() return balance.toNumber() } catch (e) { @@ -262,11 +262,8 @@ export abstract class BaseConnectionProviderManager { return 0 } } - if (account instanceof ETHAccount) { - return getERC20Balance(account.address) - } - if (account instanceof SOLAccount) { - return getSOLBalance(account.address) + if (account instanceof ETHAccount || account instanceof SOLAccount) { + return getAddressBalance(account.address) } throw Err.ChainNotYetSupported diff --git a/src/domain/file.ts b/src/domain/file.ts index 9ac372b..ea27abf 100644 --- a/src/domain/file.ts +++ b/src/domain/file.ts @@ -74,7 +74,7 @@ export class FileManager { const items = await this.sdkClient.getMessages({ messageTypes: [MessageType.store], addresses: [address], - pageSize: 1000, + pagination: 1000, }) const files = (items?.messages || []) as StoreMessage[] diff --git a/src/domain/node.ts b/src/domain/node.ts index abca75d..743874c 100644 --- a/src/domain/node.ts +++ b/src/domain/node.ts @@ -925,7 +925,7 @@ export class NodeManager { const res = await this.sdkClient.getPosts({ types: 'aleph-scoring-scores', addresses: [scoringAddress], - pageSize: 1, + pagination: 1, page: 1, }) @@ -939,7 +939,7 @@ export class NodeManager { const res = await this.sdkClient.getPosts({ types: 'aleph-network-metrics', addresses: [scoringAddress], - pageSize: 1, + pagination: 1, page: 1, }) diff --git a/src/domain/stake.ts b/src/domain/stake.ts index 1b81db2..cea6299 100644 --- a/src/domain/stake.ts +++ b/src/domain/stake.ts @@ -48,7 +48,7 @@ export class StakeManager { types: 'staking-rewards-distribution', addresses: [monitorAddress], tags: ['calculation'], - pageSize: 1, + pagination: 1, page: 1, }) @@ -68,7 +68,7 @@ export class StakeManager { types: 'staking-rewards-distribution', addresses: [senderAddress], tags: ['distribution'], - pageSize: 1, + pagination: 1, page: 1, }) diff --git a/src/helpers/utils.ts b/src/helpers/utils.ts index f773d33..d4c76ed 100644 --- a/src/helpers/utils.ts +++ b/src/helpers/utils.ts @@ -27,12 +27,12 @@ export const ellipseAddress = (address: string) => { } /** - * Get the Aleph balance for a given Ethereum address + * Get the Aleph balance for a given address * - * @param address An Ethereum address + * @param address An account address * returns The Aleph balance of the address */ -export const getERC20Balance = async (address: string) => { +export const getAddressBalance = async (address: string) => { try { const query = await fetch( `${apiServer}/api/v0/addresses/${address}/balance`, @@ -48,26 +48,6 @@ export const getERC20Balance = async (address: string) => { } } -/** - * Gets the Aleph balance for a given Solana address - * - * @param address A Solana address - * @returns The Aleph balance of the address - */ -export const getSOLBalance = async (address: string) => { - // FIXME: This is a temporary solution - try { - const query = await fetch( - `https://balance1.api.aleph.cloud/solana/${address}`, - ) - - const { balance } = await query.json() - return balance - } catch (error) { - throw Err.RequestFailed(error) - } -} - export function round(num: number, decimals = 2) { const pow = 10 ** decimals return Math.round((num + Number.EPSILON) * pow) / pow From e65ef89f29a8dc6fca91cd70975e26ebd278f9a3 Mon Sep 17 00:00:00 2001 From: gmolki Date: Tue, 10 Dec 2024 22:30:53 +0100 Subject: [PATCH 4/9] missing remove handling --- src/components/common/ExternalLink/cmp.tsx | 2 + src/components/common/ExternalLink/types.ts | 3 +- .../ComputeResourceNodeDetailPage/cmp.tsx | 408 ++--------------- .../tabs/OverviewTabContent.tsx | 412 ++++++++++++++++++ .../tabs/PoliciesTabContent.tsx | 274 ++++++++++++ src/domain/file.ts | 19 +- src/domain/message.ts | 20 +- src/domain/node.ts | 22 +- src/helpers/schemas.ts | 2 + .../form/useEditComputeResourceNodeForm.tsx | 10 + .../earn/useComputeResourceNodeDetailPage.tsx | 39 +- 11 files changed, 837 insertions(+), 374 deletions(-) create mode 100644 src/components/pages/earn/ComputeResourceNodeDetailPage/tabs/OverviewTabContent.tsx create mode 100644 src/components/pages/earn/ComputeResourceNodeDetailPage/tabs/PoliciesTabContent.tsx diff --git a/src/components/common/ExternalLink/cmp.tsx b/src/components/common/ExternalLink/cmp.tsx index 293162e..be4fc4d 100644 --- a/src/components/common/ExternalLink/cmp.tsx +++ b/src/components/common/ExternalLink/cmp.tsx @@ -9,6 +9,7 @@ export const ExternalLink = ({ color, typo, underline, + ...props }: ExternalLinkProps) => { return ( <> @@ -18,6 +19,7 @@ export const ExternalLink = ({ $color={color} $typo={typo} $underline={underline} + {...props} > {text ? text : href} diff --git a/src/components/common/ExternalLink/types.ts b/src/components/common/ExternalLink/types.ts index c0cd7a0..753e779 100644 --- a/src/components/common/ExternalLink/types.ts +++ b/src/components/common/ExternalLink/types.ts @@ -1,6 +1,7 @@ import { CoreTheme } from '@aleph-front/core' +import { AnchorHTMLAttributes } from 'react' -export type ExternalLinkProps = { +export type ExternalLinkProps = AnchorHTMLAttributes & { href: string text?: string color?: keyof CoreTheme['color'] diff --git a/src/components/pages/earn/ComputeResourceNodeDetailPage/cmp.tsx b/src/components/pages/earn/ComputeResourceNodeDetailPage/cmp.tsx index 40cd583..f9d43d6 100644 --- a/src/components/pages/earn/ComputeResourceNodeDetailPage/cmp.tsx +++ b/src/components/pages/earn/ComputeResourceNodeDetailPage/cmp.tsx @@ -1,32 +1,14 @@ import { memo } from 'react' import Head from 'next/head' import { useComputeResourceNodeDetailPage } from '@/hooks/pages/earn/useComputeResourceNodeDetailPage' -import { Button, Icon, NodeName, Tooltip } from '@aleph-front/core' -import { - ellipseAddress, - getAVAXExplorerURL, - getETHExplorerURL, - humanReadableSize, -} from '@/helpers/utils' -import Link from 'next/link' +import { Button, Icon, Tabs } from '@aleph-front/core' import NodeDetailHeader from '@/components/common/NodeDetailHeader' -import Card2, { Card2Field } from '@/components/common/Card2' -import NodeDetailStatus from '@/components/common/NodeDetailStatus' -import NodeDecentralization from '@/components/common/NodeDecentralization' -import NodeDetailEditableField from '@/components/common/NodeDetailEditableField' -import NodeDetailLink from '@/components/common/NodeDetailLink' -import { apiServer } from '@/helpers/constants' -import Image from 'next/image' -import Price from '@/components/common/Price' -import ButtonLink from '@/components/common/ButtonLink' -import { StreamNotSupportedIssue } from '@/domain/node' -import { ThreeDots } from 'react-loader-spinner' -import { useTheme } from 'styled-components' -import { LinkedStatusDotIcon } from '@/components/common/NodeLinkedNodes' -// import InfoTooltipButton from '@/components/common/InfoTooltipButton' +import OverviewTabContent from './tabs/OverviewTabContent' +import PoliciesTabContent from './tabs/PoliciesTabContent' export const ComputeResourceNodeDetailPage = () => { const { + theme, node, nodesOnSameASN, baseLatency, @@ -42,6 +24,7 @@ export const ComputeResourceNodeDetailPage = () => { rewardCtrl, streamRewardCtrl, addressCtrl, + termsAndConditionsCtrl, asnTier, nodeSpecs, nodeIssue, @@ -49,15 +32,16 @@ export const ComputeResourceNodeDetailPage = () => { isLinked, isLinkableByUser, isUnlinkableByUser, - // nodeBenchmark, + tabId, + setTabId, + tabs, + handleRemovePolicies, handleRemove, handleSubmit, handleLink, handleUnlink, } = useComputeResourceNodeDetailPage() - const theme = useTheme() - return ( <> @@ -101,345 +85,41 @@ export const ComputeResourceNodeDetailPage = () => { )} -
-
-
- - - - - - {node?.owner && ellipseAddress(node?.owner)} - - } - big - /> - - - {rewardCtrl.field.value && - ellipseAddress(rewardCtrl.field.value)} - - - } - big - /> - - - {streamRewardCtrl.field.value && - ellipseAddress(streamRewardCtrl.field.value)} - - - } - big - /> - - - {node?.address} - - - } - big - /> - - - - - - - {/* -
CPU SPEED
-
- } - > - {nodeBenchmark?.cpu.benchmark.average.toFixed(1)}s - - ) - } - /> */} - - {/* -
RAM SPEED
-
- } - > - {humanReadableSize(nodeBenchmark?.ram.speed || 0, 'MiB')} - /s - - ) - } - /> */} - - -
- - <> - Create Instance - {nodeIssue === undefined && ( - - )} - - - {!createInstanceUrl && ( -
- {nodeIssue ? ( - -
- Why is my node unavailable? -
-
- A node may be not eligible for PAYG for the - following reasons: -
-
    - {nodeIssue === StreamNotSupportedIssue.IPV6 && ( -
  • - IPv6 Egress Issue: The - node's compute resource (CRN) is unable - to establish an IPv6 egress connection. -
  • - )} - {nodeIssue === - StreamNotSupportedIssue.MinSpecs && ( -
  • - Minimum Specifications: The - node does not meet the required minimum - hardware or software specifications. -
  • - )} - {nodeIssue === - StreamNotSupportedIssue.Version && ( -
  • - Version Compatibility: Only - nodes with version 0.4.0 or higher are - eligible for selection. -
  • - )} - {nodeIssue === - StreamNotSupportedIssue.RewardAddress && ( -
  • - Stream Reward Configuration:{' '} - The node lacks a configured stream reward - address, which is necessary for operation. -
  • - )} -
-
- } - > -
- Not eligible for pay-as-you-go (PAYG) - -
- - ) : ( -
- Not eligible for pay-as-you-go (PAYG) -
- )} -
- )} - - - -
- - - {nodesOnSameASN} - {!!asnTier && ( - - )} -
- } - /> - - - - - - } - /> - - -
- -
- {!isLinked ? ( - <> - -
-
- {isLinkableByUser ? ( - - ) : ( - <>not linked - )} -
- - ) : ( - <> - {node?.parentData && ( - <> - - - - - - )} - {isUnlinkableByUser && ( - - )} - - )} -
- - - -
- - {!!asnTier && ( - - )} -
- {!!asnTier && ( -

- There are {nodesOnSameASN} nodes on this ASN. Please consider - to migrate your node to a different ASN. -

- )} -
- - - {/* - - - */} - - -
-
-
+
+ +
+ {tabId === 'overview' ? ( + + ) : tabId === 'policies' ? ( + + ) : ( + '' + )} ) } diff --git a/src/components/pages/earn/ComputeResourceNodeDetailPage/tabs/OverviewTabContent.tsx b/src/components/pages/earn/ComputeResourceNodeDetailPage/tabs/OverviewTabContent.tsx new file mode 100644 index 0000000..2a8b510 --- /dev/null +++ b/src/components/pages/earn/ComputeResourceNodeDetailPage/tabs/OverviewTabContent.tsx @@ -0,0 +1,412 @@ +import { memo } from 'react' +import { Button, Icon, NodeName, Tooltip } from '@aleph-front/core' +import { + ellipseAddress, + getAVAXExplorerURL, + getETHExplorerURL, + humanReadableSize, +} from '@/helpers/utils' +import Link from 'next/link' +import Card2, { Card2Field } from '@/components/common/Card2' +import NodeDetailStatus from '@/components/common/NodeDetailStatus' +import NodeDecentralization from '@/components/common/NodeDecentralization' +import NodeDetailEditableField from '@/components/common/NodeDetailEditableField' +import NodeDetailLink from '@/components/common/NodeDetailLink' +import { apiServer } from '@/helpers/constants' +import Image from 'next/image' +import Price from '@/components/common/Price' +import ButtonLink from '@/components/common/ButtonLink' +import { StreamNotSupportedIssue } from '@/domain/node' +import { ThreeDots } from 'react-loader-spinner' +import { LinkedStatusDotIcon } from '@/components/common/NodeLinkedNodes' +import { UseComputeResourceNodeDetailPageReturn } from '@/hooks/pages/earn/useComputeResourceNodeDetailPage' + +export type OverviewTabContentProps = Pick< + UseComputeResourceNodeDetailPageReturn, + | 'theme' + | 'node' + | 'nodesOnSameASN' + | 'baseLatency' + | 'lastMetricsCheck' + | 'calculatedRewards' + | 'creationDate' + | 'isOwner' + | 'rewardCtrl' + | 'streamRewardCtrl' + | 'addressCtrl' + | 'asnTier' + | 'nodeSpecs' + | 'nodeIssue' + | 'createInstanceUrl' + | 'isLinked' + | 'isLinkableByUser' + | 'isUnlinkableByUser' + | 'handleLink' + | 'handleUnlink' +> + +export const OverviewTabContent = ({ + theme, + node, + nodesOnSameASN, + baseLatency, + lastMetricsCheck, + calculatedRewards, + creationDate, + isOwner, + rewardCtrl, + streamRewardCtrl, + addressCtrl, + asnTier, + nodeSpecs, + nodeIssue, + createInstanceUrl, + isLinked, + isLinkableByUser, + isUnlinkableByUser, + handleLink, + handleUnlink, +}: OverviewTabContentProps) => { + return ( +
+
+
+ + + + + + {node?.owner && ellipseAddress(node?.owner)} + + } + big + /> + + + {rewardCtrl.field.value && + ellipseAddress(rewardCtrl.field.value)} + + + } + big + /> + + + {streamRewardCtrl.field.value && + ellipseAddress(streamRewardCtrl.field.value)} + + + } + big + /> + + + {node?.address} + + + } + big + /> + + + + + + + {/* +
CPU SPEED
+
+ } + > + {nodeBenchmark?.cpu.benchmark.average.toFixed(1)}s + + ) + } + /> */} + + {/* +
RAM SPEED
+
+ } + > + {humanReadableSize(nodeBenchmark?.ram.speed || 0, 'MiB')} + /s + + ) + } + /> */} + + +
+ + <> + Create Instance + {nodeIssue === undefined && ( + + )} + + + {!createInstanceUrl && ( +
+ {nodeIssue ? ( + +
+ Why is my node unavailable? +
+
+ A node may be not eligible for PAYG for the + following reasons: +
+
    + {nodeIssue === StreamNotSupportedIssue.IPV6 && ( +
  • + IPv6 Egress Issue: The + node's compute resource (CRN) is unable to + establish an IPv6 egress connection. +
  • + )} + {nodeIssue === StreamNotSupportedIssue.MinSpecs && ( +
  • + Minimum Specifications: The + node does not meet the required minimum hardware + or software specifications. +
  • + )} + {nodeIssue === StreamNotSupportedIssue.Version && ( +
  • + Version Compatibility: Only + nodes with version 0.4.0 or higher are eligible + for selection. +
  • + )} + {nodeIssue === + StreamNotSupportedIssue.RewardAddress && ( +
  • + Stream Reward Configuration:{' '} + The node lacks a configured stream reward + address, which is necessary for operation. +
  • + )} +
+
+ } + > +
+ Not eligible for pay-as-you-go (PAYG) + +
+ + ) : ( +
+ Not eligible for pay-as-you-go (PAYG) +
+ )} +
+ )} + + + +
+ + + {nodesOnSameASN} + {!!asnTier && ( + + )} +
+ } + /> + + + + + + } + /> + + +
+ +
+ {!isLinked ? ( + <> + +
+
+ {isLinkableByUser ? ( + + ) : ( + <>not linked + )} +
+ + ) : ( + <> + {node?.parentData && ( + <> + + + + + + )} + {isUnlinkableByUser && ( + + )} + + )} +
+ + + +
+ + {!!asnTier && ( + + )} +
+ {!!asnTier && ( +

+ There are {nodesOnSameASN} nodes on this ASN. Please consider to + migrate your node to a different ASN. +

+ )} +
+ + + {/* + + + */} + + +
+
+
+ ) +} +OverviewTabContent.displayName = 'OverviewTabContent' + +export default memo(OverviewTabContent) diff --git a/src/components/pages/earn/ComputeResourceNodeDetailPage/tabs/PoliciesTabContent.tsx b/src/components/pages/earn/ComputeResourceNodeDetailPage/tabs/PoliciesTabContent.tsx new file mode 100644 index 0000000..9140ce2 --- /dev/null +++ b/src/components/pages/earn/ComputeResourceNodeDetailPage/tabs/PoliciesTabContent.tsx @@ -0,0 +1,274 @@ +import { memo, useCallback, useEffect, useMemo, useState } from 'react' +import { UseComputeResourceNodeDetailPageReturn } from '@/hooks/pages/earn/useComputeResourceNodeDetailPage' +import Card2, { Card2Field } from '@/components/common/Card2' +import { + Button, + Col, + ellipseText, + FileInput, + Icon, + Row, + Spinner, +} from '@aleph-front/core' +import { apiServer } from '@/helpers/constants' +import ExternalLink from '@/components/common/ExternalLink' +import { MessageManager } from '@/domain/message' +import { MessageType } from '@aleph-sdk/message' +import { FileManager } from '@/domain/file' + +export type PoliciesTabContentProps = Pick< + UseComputeResourceNodeDetailPageReturn, + 'termsAndConditionsCtrl' | 'handleRemovePolicies' | 'node' +> + +export const PoliciesTabContent = ({ + node, + termsAndConditionsCtrl, + handleRemovePolicies, +}: PoliciesTabContentProps) => { + const { + field: { onChange, value }, + } = termsAndConditionsCtrl + + // @todo: Refactor FileInputProps to accept File too + // Adjust the file value to match FileInputProps + const fileValue = useMemo(() => { + if (value instanceof File) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const extFile = value as any + return extFile + } + return undefined + }, [value]) + + // Adjust the onChange function to accept ExtFile | ExtFile[] | undefined + const handleChange = useCallback( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (files?: any) => { + const file = Array.isArray(files) ? files[0] : files + onChange(file) + }, + [onChange], + ) + + type PoliciesRecord = { + time: Date + cid: string + name: string + } + type PoliciesHistory = Map + + const [amendMessages, setAmendMessages] = useState() + const [isLoadingAmendMessages, setIsLoadingAmendMessages] = useState(true) + const [policiesHistory, setPoliciesHistory] = useState() + const [isLoadingHistoryMessages, setIsLoadingHistoryMessages] = useState(true) + + const currentPolicies = useMemo(() => { + if (!policiesHistory) return + if (!node?.terms_and_conditions) return + + return policiesHistory.get(node.terms_and_conditions) + }, [node, policiesHistory]) + + const messageManager = useMemo(() => { + return new MessageManager() + }, []) + + const fileManager = useMemo(() => { + return new FileManager() + }, []) + + const downloadFile = useCallback( + async (fileHash: string, fileName: string) => { + const downloadedFile = await fileManager.downloadFile(fileHash) + const customDownloadUrl = window.URL.createObjectURL(downloadedFile) + const a = document.createElement('a') + a.href = customDownloadUrl + a.download = fileName + document.body.appendChild(a) + a.click() + a.remove() + window.URL.revokeObjectURL(customDownloadUrl) + }, + [fileManager], + ) + + const isCurrentVersion = useCallback( + (policies: PoliciesRecord) => { + if (!currentPolicies) return false + + return policies === currentPolicies + }, + [currentPolicies], + ) + + useEffect(() => { + const fetchMessages = async () => { + setIsLoadingAmendMessages(true) + if (!messageManager) return setAmendMessages(undefined) + if (!node) return setAmendMessages(undefined) + + try { + const { messages } = await messageManager?.getAll({ + messageTypes: [MessageType['post']], + refs: [node.hash], + addresses: [node.owner], + }) + + setAmendMessages(messages) + } catch (e) { + console.error(e) + } finally { + setIsLoadingAmendMessages(false) + } + } + + fetchMessages() + }, [messageManager, node]) + + useEffect(() => { + const fetchMessages = async () => { + setIsLoadingHistoryMessages(true) + + if (isLoadingAmendMessages) return setPoliciesHistory(undefined) + if (!messageManager) return setPoliciesHistory(undefined) + if (!amendMessages) return setPoliciesHistory(undefined) + + try { + const history = new Map() + for (const message of amendMessages) { + const policiesMessageHash = + message.content?.content?.details.terms_and_conditions + + if (!policiesMessageHash) continue + + let storeMessageContent + try { + const { content } = + await messageManager?.get(policiesMessageHash) + storeMessageContent = content + } catch (e) { + console.error(e) + } + + if (!storeMessageContent) continue + + history.set(policiesMessageHash, { + time: new Date(message.content.time * 1000), + cid: storeMessageContent.item_hash, + name: storeMessageContent.metadata?.name as string, + }) + } + + const sortedHistory = new Map( + Array.from(history).sort( + ([, { time: a }], [, { time: b }]) => b.getTime() - a.getTime(), + ), + ) + + setPoliciesHistory(sortedHistory) + } catch (e) { + console.error(e) + } finally { + setIsLoadingHistoryMessages(false) + } + } + + fetchMessages() + }, [amendMessages, isLoadingAmendMessages, messageManager]) + + return ( +
+ + + + + + + + + + + +
+ History +
+ {isLoadingHistoryMessages ? ( + + ) : ( + policiesHistory && ( +
    + {Array.from(policiesHistory).map(([, policies], index) => ( +
  1. +
    + {isCurrentVersion(policies) + ? 'Now' + : policies.time.toLocaleDateString()} +
    + + downloadFile( + policies.cid, + policies.name || policies.cid, + ) + } + target="_self" + /> + {/* Don't render left line only for last element */} + {index < policiesHistory.size - 1 && ( +
    + )} +
  2. + ))} +
+ ) + )} + +
+
+ ) +} +PoliciesTabContent.displayName = 'PoliciesTabContent' + +export default memo(PoliciesTabContent) diff --git a/src/domain/file.ts b/src/domain/file.ts index ea27abf..141854e 100644 --- a/src/domain/file.ts +++ b/src/domain/file.ts @@ -107,7 +107,9 @@ export class FileManager { } } - async uploadFile(fileObject: File): Promise { + async uploadFile( + fileObject: File, + ): Promise<{ contentItemHash: string; messageItemHash: string }> { if (!(this.sdkClient instanceof AuthenticatedAlephHttpClient)) throw new Error('Account needed to perform this action') @@ -117,8 +119,21 @@ export class FileManager { const message = await this.sdkClient.createStore({ channel, fileObject: buffer, + metadata: { + name: fileObject.name, + format: fileObject.type, + }, }) - return message.content.item_hash + return { + contentItemHash: message.content.item_hash, + messageItemHash: message.item_hash, + } + } + + async downloadFile(fileHash: string): Promise { + const file = await this.sdkClient.downloadFile(fileHash) + + return new File([file], fileHash) } } diff --git a/src/domain/message.ts b/src/domain/message.ts index bbe6352..f6c8563 100644 --- a/src/domain/message.ts +++ b/src/domain/message.ts @@ -5,10 +5,11 @@ import { } from '@aleph-sdk/client' import Err from '../helpers/errors' import { apiServer, defaultAccountChannel } from '@/helpers/constants' +import { GetMessagesConfiguration, MessageType } from '@aleph-sdk/message' export class MessageManager { constructor( - protected account: Account, + protected account?: Account, protected channel = defaultAccountChannel, protected sdkClient: | AlephHttpClient @@ -20,9 +21,9 @@ export class MessageManager { /** * Returns an aleph program message for a given hash */ - async get(hash: string) { + async get(hash: string) { try { - const msg = await this.sdkClient.getMessage(hash) + const msg = await this.sdkClient.getMessage(hash) return msg } catch (error) { @@ -30,6 +31,19 @@ export class MessageManager { } } + /** + * Returns aleph program messages for a given configuration + */ + async getAll(config: GetMessagesConfiguration) { + try { + const msgs = await this.sdkClient.getMessages(config) + + return msgs + } catch (error) { + throw Err.RequestFailed(error) + } + } + /** * Deletes a VM using a forget message */ diff --git a/src/domain/node.ts b/src/domain/node.ts index 743874c..b8691b9 100644 --- a/src/domain/node.ts +++ b/src/domain/node.ts @@ -87,6 +87,7 @@ export type CRN = BaseNode & { metricsData?: CRNMetrics parentData?: CCN stream_reward?: string + terms_and_conditions?: string } export type AlephNode = CCN | CRN @@ -189,6 +190,7 @@ export type UpdateCCN = BaseUpdateNode & { export type UpdateCRN = BaseUpdateNode & { address?: string stream_reward?: string + terms_and_conditions?: string | File } export type UpdateAlephNode = UpdateCCN | UpdateCRN @@ -539,11 +541,27 @@ export class NodeManager { } if (details.picture instanceof File) { - details.picture = await this.fileManager.uploadFile(details.picture) + const { contentItemHash } = await this.fileManager.uploadFile( + details.picture, + ) + details.picture = contentItemHash } if (details.banner instanceof File) { - details.banner = await this.fileManager.uploadFile(details.banner) + const { contentItemHash } = await this.fileManager.uploadFile( + details.banner, + ) + details.banner = contentItemHash + } + + if ( + 'terms_and_conditions' in details && + details.terms_and_conditions instanceof File + ) { + const { messageItemHash } = await this.fileManager.uploadFile( + details.terms_and_conditions, + ) + details.terms_and_conditions = messageItemHash } const res = await this.sdkClient.createPost({ diff --git a/src/helpers/schemas.ts b/src/helpers/schemas.ts index 51aca32..4fb6dc7 100644 --- a/src/helpers/schemas.ts +++ b/src/helpers/schemas.ts @@ -43,6 +43,7 @@ export const newCRNSchema = z.object({ }) // -------------------------- +const fileSchema = z.custom() export const imgFileSchema = z .custom((val) => val instanceof File, 'Invalid file type') @@ -82,4 +83,5 @@ export const updateCCNSchema = updateBaseNodeSchema.extend({ export const updateCRNSchema = updateBaseNodeSchema.extend({ address: optionalString(urlSchema), stream_reward: optionalString(ethereumAddressSchema), + terms_and_conditions: optionalStringSchema.or(fileSchema), }) diff --git a/src/hooks/form/useEditComputeResourceNodeForm.tsx b/src/hooks/form/useEditComputeResourceNodeForm.tsx index b99eceb..c3ba778 100644 --- a/src/hooks/form/useEditComputeResourceNodeForm.tsx +++ b/src/hooks/form/useEditComputeResourceNodeForm.tsx @@ -50,6 +50,10 @@ export type UseEditComputeResourceNodeFormReturn = { UseEditComputeResourceNodeFormState, 'registration_url' > + termsAndConditionsCtrl: UseControllerReturn< + UseEditComputeResourceNodeFormState, + 'terms_and_conditions' + > errors: FieldErrors isDirty: boolean handleSubmit: (e: FormEvent) => Promise @@ -176,6 +180,11 @@ export function useEditComputeResourceNodeForm({ name: 'registration_url', }) + const termsAndConditionsCtrl = useController({ + control, + name: 'terms_and_conditions', + }) + return { values, control, @@ -189,6 +198,7 @@ export function useEditComputeResourceNodeForm({ authorizedCtrl, lockedCtrl, registrationUrlCtrl, + termsAndConditionsCtrl, errors, isDirty, handleSubmit, diff --git a/src/hooks/pages/earn/useComputeResourceNodeDetailPage.tsx b/src/hooks/pages/earn/useComputeResourceNodeDetailPage.tsx index d1b715a..8383f38 100644 --- a/src/hooks/pages/earn/useComputeResourceNodeDetailPage.tsx +++ b/src/hooks/pages/earn/useComputeResourceNodeDetailPage.tsx @@ -9,7 +9,7 @@ import { } from '@/domain/node' import { useRouter } from 'next/router' import { useComputeResourceNode } from '@/hooks/common/useComputeResourceNode' -import { useCallback, useMemo } from 'react' +import { useCallback, useMemo, useState } from 'react' import { UseNodeDetailReturn, useNodeDetail, @@ -29,6 +29,8 @@ import { consoleNewInstanceUrl } from '@/helpers/constants' import { convertByteUnits } from '@/helpers/utils' import { useRequestCRNIps } from '@/hooks/common/useRequestEntity/useRequestCRNIps' import { StakeManager } from '@/domain/stake' +import { TabsProps } from '@aleph-front/core' +import { DefaultTheme, useTheme } from 'styled-components' // import { useRequestCRNBenchmark } from '@/hooks/common/useRequestEntity/useRequestCRNBenchmark' export type UseComputeResourceNodeDetailPageProps = { @@ -37,6 +39,7 @@ export type UseComputeResourceNodeDetailPageProps = { export type UseComputeResourceNodeDetailPageReturn = UseNodeDetailReturn & UseEditComputeResourceNodeFormReturn & { + theme: DefaultTheme nodes?: CRN[] node?: CRN userNode?: CCN @@ -49,12 +52,17 @@ export type UseComputeResourceNodeDetailPageReturn = UseNodeDetailReturn & isLinked?: boolean isLinkableByUser?: boolean isUnlinkableByUser?: boolean + tabs: TabsProps['tabs'] + tabId: string + setTabId: (tab: string) => void + handleRemovePolicies: () => void handleRemove: () => void handleLink: () => Promise handleUnlink: () => Promise } export function useComputeResourceNodeDetailPage(): UseComputeResourceNodeDetailPageReturn { + const theme = useTheme() const router = useRouter() const { hash } = router.query @@ -84,6 +92,23 @@ export function useComputeResourceNodeDetailPage(): UseComputeResourceNodeDetail // ----------------------------- + const [tabId, setTabId] = useState('overview') + + const tabs: TabsProps['tabs'] = useMemo(() => { + return [ + { + id: 'overview', + name: 'Overview', + }, + { + id: 'policies', + name: 'Policies', + }, + ] + }, []) + + // ----------------------------- + const { isLinked: isLinkedCheck, isLinkableByUser: isLinkableByUserCheck, @@ -247,12 +272,19 @@ export function useComputeResourceNodeDetailPage(): UseComputeResourceNodeDetail picture: node?.picture, banner: node?.banner, address: node?.address, + terms_and_conditions: undefined, } }, [node]) const formProps = useEditComputeResourceNodeForm({ defaultValues }) + //@todo: implement this + const handleRemovePolicies = useCallback(async () => { + return true + }, []) + return { + theme, nodes, node, userNode, @@ -261,10 +293,13 @@ export function useComputeResourceNodeDetailPage(): UseComputeResourceNodeDetail nodeSpecs, nodeIssue, createInstanceUrl, - // nodeBenchmark, isLinked, isLinkableByUser, isUnlinkableByUser, + tabs, + tabId, + setTabId, + handleRemovePolicies, handleLink, handleUnlink, ...formProps, From 05eaaf80184f274ef3e9ebd6e9186248fb543181 Mon Sep 17 00:00:00 2001 From: gmolki Date: Wed, 11 Dec 2024 11:46:50 +0100 Subject: [PATCH 5/9] add remove handling --- .../tabs/PoliciesTabContent.tsx | 12 +++++------- .../pages/earn/useComputeResourceNodeDetailPage.tsx | 12 ++++++++---- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/components/pages/earn/ComputeResourceNodeDetailPage/tabs/PoliciesTabContent.tsx b/src/components/pages/earn/ComputeResourceNodeDetailPage/tabs/PoliciesTabContent.tsx index 9140ce2..ac002bc 100644 --- a/src/components/pages/earn/ComputeResourceNodeDetailPage/tabs/PoliciesTabContent.tsx +++ b/src/components/pages/earn/ComputeResourceNodeDetailPage/tabs/PoliciesTabContent.tsx @@ -78,7 +78,7 @@ export const PoliciesTabContent = ({ return new FileManager() }, []) - const downloadFile = useCallback( + const handleDownloadFile = useCallback( async (fileHash: string, fileName: string) => { const downloadedFile = await fileManager.downloadFile(fileHash) const customDownloadUrl = window.URL.createObjectURL(downloadedFile) @@ -140,7 +140,7 @@ export const PoliciesTabContent = ({ const policiesMessageHash = message.content?.content?.details.terms_and_conditions - if (!policiesMessageHash) continue + if (!policiesMessageHash) break let storeMessageContent try { @@ -185,9 +185,7 @@ export const PoliciesTabContent = ({ - downloadFile( + handleDownloadFile( policies.cid, policies.name || policies.cid, ) diff --git a/src/hooks/pages/earn/useComputeResourceNodeDetailPage.tsx b/src/hooks/pages/earn/useComputeResourceNodeDetailPage.tsx index 8383f38..4d993d4 100644 --- a/src/hooks/pages/earn/useComputeResourceNodeDetailPage.tsx +++ b/src/hooks/pages/earn/useComputeResourceNodeDetailPage.tsx @@ -7,7 +7,7 @@ import { NodeManager, StreamNotSupportedIssue, } from '@/domain/node' -import { useRouter } from 'next/router' +import Router, { useRouter } from 'next/router' import { useComputeResourceNode } from '@/hooks/common/useComputeResourceNode' import { useCallback, useMemo, useState } from 'react' import { @@ -272,7 +272,7 @@ export function useComputeResourceNodeDetailPage(): UseComputeResourceNodeDetail picture: node?.picture, banner: node?.banner, address: node?.address, - terms_and_conditions: undefined, + terms_and_conditions: node?.terms_and_conditions, } }, [node]) @@ -280,8 +280,12 @@ export function useComputeResourceNodeDetailPage(): UseComputeResourceNodeDetail //@todo: implement this const handleRemovePolicies = useCallback(async () => { - return true - }, []) + await nodeManager.updateComputeResourceNode({ + ...formProps.values, + terms_and_conditions: '', + }) + Router.reload() + }, [formProps.values, nodeManager]) return { theme, From 94275692b1059e2ce37e7da7377e5a9d0e5407d7 Mon Sep 17 00:00:00 2001 From: gmolki Date: Tue, 17 Dec 2024 08:50:14 +0100 Subject: [PATCH 6/9] refactor: use ipfs for upload file --- src/domain/file.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/domain/file.ts b/src/domain/file.ts index 141854e..7b76ded 100644 --- a/src/domain/file.ts +++ b/src/domain/file.ts @@ -1,6 +1,6 @@ import { Account } from '@aleph-sdk/account' import { apiServer, channel, defaultAccountChannel } from '@/helpers/constants' -import { MessageType, StoreMessage } from '@aleph-sdk/message' +import { ItemType, MessageType, StoreMessage } from '@aleph-sdk/message' import { AlephHttpClient, AuthenticatedAlephHttpClient, @@ -119,6 +119,7 @@ export class FileManager { const message = await this.sdkClient.createStore({ channel, fileObject: buffer, + storageEngine: ItemType.ipfs, metadata: { name: fileObject.name, format: fileObject.type, From a4d8d80a173462af59f0b40bbf581f0387b3ca64 Mon Sep 17 00:00:00 2001 From: gmolki Date: Tue, 17 Dec 2024 09:47:23 +0100 Subject: [PATCH 7/9] refactor: policies tab content --- .../ComputeResourceNodeDetailPage/cmp.tsx | 2 +- .../tabs/OverviewTabContent.tsx | 2 +- .../tabs/PoliciesTabContent.tsx | 203 ++------------- .../useComputeResourceNodeDetailPage.tsx | 0 .../usePoliciesTabContent.ts | 233 ++++++++++++++++++ 5 files changed, 255 insertions(+), 185 deletions(-) rename src/hooks/pages/earn/{ => ComputeResourceNodeDetailPage}/useComputeResourceNodeDetailPage.tsx (100%) create mode 100644 src/hooks/pages/earn/ComputeResourceNodeDetailPage/usePoliciesTabContent.ts diff --git a/src/components/pages/earn/ComputeResourceNodeDetailPage/cmp.tsx b/src/components/pages/earn/ComputeResourceNodeDetailPage/cmp.tsx index f9d43d6..afb55b5 100644 --- a/src/components/pages/earn/ComputeResourceNodeDetailPage/cmp.tsx +++ b/src/components/pages/earn/ComputeResourceNodeDetailPage/cmp.tsx @@ -1,6 +1,6 @@ import { memo } from 'react' import Head from 'next/head' -import { useComputeResourceNodeDetailPage } from '@/hooks/pages/earn/useComputeResourceNodeDetailPage' +import { useComputeResourceNodeDetailPage } from '@/hooks/pages/earn/ComputeResourceNodeDetailPage/useComputeResourceNodeDetailPage' import { Button, Icon, Tabs } from '@aleph-front/core' import NodeDetailHeader from '@/components/common/NodeDetailHeader' import OverviewTabContent from './tabs/OverviewTabContent' diff --git a/src/components/pages/earn/ComputeResourceNodeDetailPage/tabs/OverviewTabContent.tsx b/src/components/pages/earn/ComputeResourceNodeDetailPage/tabs/OverviewTabContent.tsx index 2a8b510..fe2a7d4 100644 --- a/src/components/pages/earn/ComputeResourceNodeDetailPage/tabs/OverviewTabContent.tsx +++ b/src/components/pages/earn/ComputeResourceNodeDetailPage/tabs/OverviewTabContent.tsx @@ -19,7 +19,7 @@ import ButtonLink from '@/components/common/ButtonLink' import { StreamNotSupportedIssue } from '@/domain/node' import { ThreeDots } from 'react-loader-spinner' import { LinkedStatusDotIcon } from '@/components/common/NodeLinkedNodes' -import { UseComputeResourceNodeDetailPageReturn } from '@/hooks/pages/earn/useComputeResourceNodeDetailPage' +import { UseComputeResourceNodeDetailPageReturn } from '@/hooks/pages/earn/ComputeResourceNodeDetailPage/useComputeResourceNodeDetailPage' export type OverviewTabContentProps = Pick< UseComputeResourceNodeDetailPageReturn, diff --git a/src/components/pages/earn/ComputeResourceNodeDetailPage/tabs/PoliciesTabContent.tsx b/src/components/pages/earn/ComputeResourceNodeDetailPage/tabs/PoliciesTabContent.tsx index ac002bc..ab5faba 100644 --- a/src/components/pages/earn/ComputeResourceNodeDetailPage/tabs/PoliciesTabContent.tsx +++ b/src/components/pages/earn/ComputeResourceNodeDetailPage/tabs/PoliciesTabContent.tsx @@ -1,5 +1,5 @@ -import { memo, useCallback, useEffect, useMemo, useState } from 'react' -import { UseComputeResourceNodeDetailPageReturn } from '@/hooks/pages/earn/useComputeResourceNodeDetailPage' +import { memo } from 'react' +import { UseComputeResourceNodeDetailPageReturn } from '@/hooks/pages/earn/ComputeResourceNodeDetailPage/useComputeResourceNodeDetailPage' import Card2, { Card2Field } from '@/components/common/Card2' import { Button, @@ -10,200 +10,37 @@ import { Row, Spinner, } from '@aleph-front/core' -import { apiServer } from '@/helpers/constants' import ExternalLink from '@/components/common/ExternalLink' -import { MessageManager } from '@/domain/message' -import { MessageType } from '@aleph-sdk/message' -import { FileManager } from '@/domain/file' +import { usePoliciesTabContent } from '@/hooks/pages/earn/ComputeResourceNodeDetailPage/usePoliciesTabContent' export type PoliciesTabContentProps = Pick< UseComputeResourceNodeDetailPageReturn, 'termsAndConditionsCtrl' | 'handleRemovePolicies' | 'node' > -export const PoliciesTabContent = ({ - node, - termsAndConditionsCtrl, - handleRemovePolicies, -}: PoliciesTabContentProps) => { +export const PoliciesTabContent = (props: PoliciesTabContentProps) => { const { - field: { onChange, value }, - } = termsAndConditionsCtrl - - // @todo: Refactor FileInputProps to accept File too - // Adjust the file value to match FileInputProps - const fileValue = useMemo(() => { - if (value instanceof File) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const extFile = value as any - return extFile - } - return undefined - }, [value]) - - // Adjust the onChange function to accept ExtFile | ExtFile[] | undefined - const handleChange = useCallback( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (files?: any) => { - const file = Array.isArray(files) ? files[0] : files - onChange(file) - }, - [onChange], - ) - - type PoliciesRecord = { - time: Date - cid: string - name: string - } - type PoliciesHistory = Map - - const [amendMessages, setAmendMessages] = useState() - const [isLoadingAmendMessages, setIsLoadingAmendMessages] = useState(true) - const [policiesHistory, setPoliciesHistory] = useState() - const [isLoadingHistoryMessages, setIsLoadingHistoryMessages] = useState(true) - - const currentPolicies = useMemo(() => { - if (!policiesHistory) return - if (!node?.terms_and_conditions) return - - return policiesHistory.get(node.terms_and_conditions) - }, [node, policiesHistory]) - - const messageManager = useMemo(() => { - return new MessageManager() - }, []) - - const fileManager = useMemo(() => { - return new FileManager() - }, []) - - const handleDownloadFile = useCallback( - async (fileHash: string, fileName: string) => { - const downloadedFile = await fileManager.downloadFile(fileHash) - const customDownloadUrl = window.URL.createObjectURL(downloadedFile) - const a = document.createElement('a') - a.href = customDownloadUrl - a.download = fileName - document.body.appendChild(a) - a.click() - a.remove() - window.URL.revokeObjectURL(customDownloadUrl) - }, - [fileManager], - ) - - const isCurrentVersion = useCallback( - (policies: PoliciesRecord) => { - if (!currentPolicies) return false - - return policies === currentPolicies - }, - [currentPolicies], - ) - - useEffect(() => { - const fetchMessages = async () => { - setIsLoadingAmendMessages(true) - if (!messageManager) return setAmendMessages(undefined) - if (!node) return setAmendMessages(undefined) - - try { - const { messages } = await messageManager?.getAll({ - messageTypes: [MessageType['post']], - refs: [node.hash], - addresses: [node.owner], - }) - - setAmendMessages(messages) - } catch (e) { - console.error(e) - } finally { - setIsLoadingAmendMessages(false) - } - } - - fetchMessages() - }, [messageManager, node]) - - useEffect(() => { - const fetchMessages = async () => { - setIsLoadingHistoryMessages(true) - - if (isLoadingAmendMessages) return setPoliciesHistory(undefined) - if (!messageManager) return setPoliciesHistory(undefined) - if (!amendMessages) return setPoliciesHistory(undefined) - - try { - const history = new Map() - for (const message of amendMessages) { - const policiesMessageHash = - message.content?.content?.details.terms_and_conditions - - if (!policiesMessageHash) break - - let storeMessageContent - try { - const { content } = - await messageManager?.get(policiesMessageHash) - storeMessageContent = content - } catch (e) { - console.error(e) - } - - if (!storeMessageContent) continue - - history.set(policiesMessageHash, { - time: new Date(message.content.time * 1000), - cid: storeMessageContent.item_hash, - name: storeMessageContent.metadata?.name as string, - }) - } - - const sortedHistory = new Map( - Array.from(history).sort( - ([, { time: a }], [, { time: b }]) => b.getTime() - a.getTime(), - ), - ) - - setPoliciesHistory(sortedHistory) - } catch (e) { - console.error(e) - } finally { - setIsLoadingHistoryMessages(false) - } - } - - fetchMessages() - }, [amendMessages, isLoadingAmendMessages, messageManager]) + documentName, + documentCID, + documentLink, + termsAndConditionsCtrl, + isLoadingHistoryMessages, + policiesHistory, + fileValue, + isCurrentVersion, + handleFileChange, + handleDownloadFile, + handleRemovePolicies, + } = usePoliciesTabContent(props) return (
- - - + + + - + {isOwner && ( + <> + + +
+ Max. file size is 100 MB +
+ + )}
diff --git a/src/hooks/pages/earn/ComputeResourceNodeDetailPage/usePoliciesTabContent.ts b/src/hooks/pages/earn/ComputeResourceNodeDetailPage/usePoliciesTabContent.ts index 6fa609d..814a95f 100644 --- a/src/hooks/pages/earn/ComputeResourceNodeDetailPage/usePoliciesTabContent.ts +++ b/src/hooks/pages/earn/ComputeResourceNodeDetailPage/usePoliciesTabContent.ts @@ -7,10 +7,11 @@ import { FileManager } from '@/domain/file' export type usePoliciesTabContentProps = Pick< UseComputeResourceNodeDetailPageReturn, - 'termsAndConditionsCtrl' | 'handleRemovePolicies' | 'node' + 'termsAndConditionsCtrl' | 'handleRemovePolicies' | 'node' | 'isOwner' > export type usePoliciesTabContentReturn = { + isOwner?: boolean documentName: string documentCID: string documentLink: string @@ -18,6 +19,7 @@ export type usePoliciesTabContentReturn = { termsAndConditionsCtrl: UseComputeResourceNodeDetailPageReturn['termsAndConditionsCtrl'] isLoadingHistoryMessages: boolean fileValue: any + removePoliciesDisabled: boolean isCurrentVersion: (policies: PoliciesRecord) => boolean handleFileChange: (files?: any) => void handleDownloadFile: (fileHash: string, fileName: string) => Promise @@ -35,6 +37,7 @@ export function usePoliciesTabContent({ node, termsAndConditionsCtrl, handleRemovePolicies, + ...props }: usePoliciesTabContentProps): usePoliciesTabContentReturn { const { field: { onChange, value }, @@ -88,6 +91,11 @@ export function usePoliciesTabContent({ [isLoadingHistoryMessages, currentPolicies], ) + const removePoliciesDisabled = useMemo( + () => !policiesHistory?.size, + [policiesHistory], + ) + // @todo: Refactor FileInputProps to accept File too // Adjust the file value to match FileInputProps const fileValue = useMemo(() => { @@ -225,9 +233,11 @@ export function usePoliciesTabContent({ termsAndConditionsCtrl, isLoadingHistoryMessages, fileValue, + removePoliciesDisabled, isCurrentVersion, handleFileChange, handleDownloadFile, handleRemovePolicies, + ...props, } }