From f8ffcbac753f3e65dcc5d4ef0147a7b774fe2366 Mon Sep 17 00:00:00 2001 From: Bogdan Fazakas Date: Wed, 1 Sep 2021 14:56:34 +0300 Subject: [PATCH] Account metadata header (#776) * get all neded data for the header from 3box, aqua and subgraph * fix tvl display error * WIP metadata header styling * added more styling for the header * make page title optional so we can remove it on account page * stroke change for svg images and default values * more styling added to the header * fixed linter * added ocean balance to tvl * update styling for statistcs * fixed eror for go to my account from another account page * updated styling for mobile use * wip show more on explorer links and description * properly display read more for explorer links and description * replaced show more with 3box redirect on description * change accounts default picture and check links length before display element * use optional on links * grid cleanup, new number unit, split up stats * rename all the things, more profile header styling * visual hierarchy, improve image loading experience * layout flow & visual tweaks * more description * replaced account route with profile when accesing a profile by the eth address * use account id from url if exists when fetching data * bump @oceanprotocol/art to v3.2.0 * styling, fallbacks, edge case fixes * clean up Publisher atom, link to profile page * fixed issue when switching to my profile from another profile * output accountId, make it copyable, remove stats icons * render tweaks, markup cleanup * add 3box reference * mobile tabs spacing tweaks * text flow and spacing tweaks Co-authored-by: Matthias Kretschmann --- _redirects | 2 +- content/pages/{account.json => profile.json} | 0 content/site.json | 4 +- gatsby-node.js | 4 +- package-lock.json | 108 +++++++++++++++++ package.json | 1 + src/components/atoms/Copy.module.css | 35 ++++++ src/components/atoms/Copy.tsx | 33 ++++++ .../atoms/Publisher/ProfileDetails.module.css | 72 ------------ .../atoms/Publisher/ProfileDetails.tsx | 51 -------- .../atoms/Publisher/index.module.css | 31 ----- src/components/atoms/Publisher/index.tsx | 33 +----- src/components/atoms/Tabs.module.css | 11 +- .../molecules/NumberUnit.module.css | 48 ++++++++ src/components/molecules/NumberUnit.tsx | 45 ++++++++ .../organisms/AssetActions/Compute/index.tsx | 4 +- src/components/pages/Account/index.tsx | 23 ---- .../pages/Profile/Account.module.css | 62 ++++++++++ src/components/pages/Profile/Account.tsx | 75 ++++++++++++ .../pages/Profile/Header.module.css | 54 +++++++++ src/components/pages/Profile/Header.tsx | 109 ++++++++++++++++++ .../History/ComputeJobs/Details.module.css | 0 .../History/ComputeJobs/Details.tsx | 1 - .../History/ComputeJobs/Results.module.css | 0 .../History/ComputeJobs/Results.tsx | 0 .../History/ComputeJobs/index.module.css | 0 .../History/ComputeJobs/index.tsx | 5 +- .../History/Downloads.tsx | 1 - .../History/PoolShares.module.css | 0 .../History/PoolShares.tsx | 18 +-- .../History/PublishedList.tsx | 2 +- .../History/index.module.css | 2 +- .../{Account => Profile}/History/index.tsx | 17 ++- .../Profile}/PublisherLinks.module.css | 7 +- .../Profile}/PublisherLinks.tsx | 16 ++- src/components/pages/Profile/Stats.module.css | 6 + src/components/pages/Profile/Stats.tsx | 103 +++++++++++++++++ src/components/pages/Profile/index.tsx | 16 +++ src/components/templates/Page.tsx | 2 +- src/pages/account/index.tsx | 35 ------ src/pages/profile/index.tsx | 39 +++++++ src/utils/subgraph.ts | 107 +++++++++++++++++ tests/unit/__fixtures__/siteMetadata.json | 4 +- 43 files changed, 894 insertions(+), 292 deletions(-) rename content/pages/{account.json => profile.json} (100%) create mode 100644 src/components/atoms/Copy.module.css create mode 100644 src/components/atoms/Copy.tsx delete mode 100644 src/components/atoms/Publisher/ProfileDetails.module.css delete mode 100644 src/components/atoms/Publisher/ProfileDetails.tsx create mode 100644 src/components/molecules/NumberUnit.module.css create mode 100644 src/components/molecules/NumberUnit.tsx delete mode 100644 src/components/pages/Account/index.tsx create mode 100644 src/components/pages/Profile/Account.module.css create mode 100644 src/components/pages/Profile/Account.tsx create mode 100644 src/components/pages/Profile/Header.module.css create mode 100644 src/components/pages/Profile/Header.tsx rename src/components/pages/{Account => Profile}/History/ComputeJobs/Details.module.css (100%) rename src/components/pages/{Account => Profile}/History/ComputeJobs/Details.tsx (98%) rename src/components/pages/{Account => Profile}/History/ComputeJobs/Results.module.css (100%) rename src/components/pages/{Account => Profile}/History/ComputeJobs/Results.tsx (100%) rename src/components/pages/{Account => Profile}/History/ComputeJobs/index.module.css (100%) rename src/components/pages/{Account => Profile}/History/ComputeJobs/index.tsx (98%) rename src/components/pages/{Account => Profile}/History/Downloads.tsx (98%) rename src/components/pages/{Account => Profile}/History/PoolShares.module.css (100%) rename src/components/pages/{Account => Profile}/History/PoolShares.tsx (92%) rename src/components/pages/{Account => Profile}/History/PublishedList.tsx (96%) rename src/components/pages/{Account => Profile}/History/index.module.css (98%) rename src/components/pages/{Account => Profile}/History/index.tsx (83%) rename src/components/{atoms/Publisher => pages/Profile}/PublisherLinks.module.css (69%) rename src/components/{atoms/Publisher => pages/Profile}/PublisherLinks.tsx (80%) create mode 100644 src/components/pages/Profile/Stats.module.css create mode 100644 src/components/pages/Profile/Stats.tsx create mode 100644 src/components/pages/Profile/index.tsx delete mode 100644 src/pages/account/index.tsx create mode 100644 src/pages/profile/index.tsx diff --git a/_redirects b/_redirects index b810c2224..db198413a 100644 --- a/_redirects +++ b/_redirects @@ -1,2 +1,2 @@ /asset/* /asset/index.html 200 -/account/* /account/index.html 200 \ No newline at end of file +/profile/* /profile/index.html 200 \ No newline at end of file diff --git a/content/pages/account.json b/content/pages/profile.json similarity index 100% rename from content/pages/account.json rename to content/pages/profile.json diff --git a/content/site.json b/content/site.json index 84e2ede5c..7cef28e78 100644 --- a/content/site.json +++ b/content/site.json @@ -12,8 +12,8 @@ "link": "/publish" }, { - "name": "My Account", - "link": "/account" + "name": "Profile", + "link": "/profile" } ], "warning": { diff --git a/gatsby-node.js b/gatsby-node.js index cc88386ba..cb611c715 100644 --- a/gatsby-node.js +++ b/gatsby-node.js @@ -34,14 +34,14 @@ exports.onCreatePage = async ({ page, actions }) => { // page.matchPath is a special key that's used for matching pages // only on the client. const handleClientSideOnlyAsset = page.path.match(/^\/asset/) - const handleClientSideOnlyAccount = page.path.match(/^\/account/) + const handleClientSideOnlyAccount = page.path.match(/^\/profile/) if (handleClientSideOnlyAsset) { page.matchPath = '/asset/*' // Update the page. createPage(page) } else if (handleClientSideOnlyAccount) { - page.matchPath = '/account/*' + page.matchPath = '/profile/*' createPage(page) } } diff --git a/package-lock.json b/package-lock.json index 4574eb4f3..94e78b378 100644 --- a/package-lock.json +++ b/package-lock.json @@ -55,6 +55,7 @@ "query-string": "^7.0.0", "react": "^17.0.2", "react-chartjs-2": "^2.11.2", + "react-clipboard.js": "^2.0.16", "react-data-table-component": "^6.11.7", "react-dom": "^17.0.2", "react-dotdotdot": "^1.3.1", @@ -10524,6 +10525,15 @@ "classnames": "*" } }, + "node_modules/@types/clipboard": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/clipboard/-/clipboard-2.0.7.tgz", + "integrity": "sha512-VwVFUHlneOsWfv/GaaY7Kwk4XasDqkAlyFQtsHxnOw0yyBYWTrlEXtmb9RtC+VFBCdtuOeIXECmELNd5RrKp/g==", + "deprecated": "This is a stub types definition. clipboard provides its own type definitions, so you do not need this installed.", + "dependencies": { + "clipboard": "*" + } + }, "node_modules/@types/common-tags": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/@types/common-tags/-/common-tags-1.8.0.tgz", @@ -16998,6 +17008,16 @@ "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz", "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==" }, + "node_modules/clipboard": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.8.tgz", + "integrity": "sha512-Y6WO0unAIQp5bLmk1zdThRhgJt/x3ks6f30s3oE3H1mgIEU33XyQjEf8gsf6DxC7NPX8Y1SsNWjUjL/ywLnnbQ==", + "dependencies": { + "good-listener": "^1.2.2", + "select": "^1.1.2", + "tiny-emitter": "^2.0.0" + } + }, "node_modules/clipboardy": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-2.3.0.tgz", @@ -19004,6 +19024,11 @@ "node": ">=0.4.0" } }, + "node_modules/delegate": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz", + "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==" + }, "node_modules/delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", @@ -28778,6 +28803,14 @@ "node": ">= 0.10" } }, + "node_modules/good-listener": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz", + "integrity": "sha1-1TswzfkxPf+33JoNR3CWqm0UXFA=", + "dependencies": { + "delegate": "^3.1.2" + } + }, "node_modules/google-protobuf": { "version": "3.17.3", "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.17.3.tgz", @@ -44904,6 +44937,20 @@ "react-dom": "^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, + "node_modules/react-clipboard.js": { + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/react-clipboard.js/-/react-clipboard.js-2.0.16.tgz", + "integrity": "sha512-COwmnbrRbl8y4f/SjtonnJTeBRD03YzsHBL5on8iL/uyjERsMkKC7djtfmns7iRAbzadn/84MdpaqaQ3ITP47g==", + "dependencies": { + "@types/clipboard": "^2.0.1", + "clipboard": "^2.0.0", + "prop-types": "^15.5.0" + }, + "peerDependencies": { + "react": ">=15.5.0", + "react-dom": ">=15.0.0" + } + }, "node_modules/react-data-table-component": { "version": "6.11.7", "resolved": "https://registry.npmjs.org/react-data-table-component/-/react-data-table-component-6.11.7.tgz", @@ -47656,6 +47703,11 @@ "seek-table": "bin/seek-bzip-table" } }, + "node_modules/select": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz", + "integrity": "sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0=" + }, "node_modules/select-hose": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", @@ -51465,6 +51517,11 @@ "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=" }, + "node_modules/tiny-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", + "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==" + }, "node_modules/tiny-queue": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/tiny-queue/-/tiny-queue-0.2.1.tgz", @@ -66972,6 +67029,14 @@ "classnames": "*" } }, + "@types/clipboard": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/clipboard/-/clipboard-2.0.7.tgz", + "integrity": "sha512-VwVFUHlneOsWfv/GaaY7Kwk4XasDqkAlyFQtsHxnOw0yyBYWTrlEXtmb9RtC+VFBCdtuOeIXECmELNd5RrKp/g==", + "requires": { + "clipboard": "*" + } + }, "@types/common-tags": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/@types/common-tags/-/common-tags-1.8.0.tgz", @@ -72373,6 +72438,16 @@ "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz", "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==" }, + "clipboard": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.8.tgz", + "integrity": "sha512-Y6WO0unAIQp5bLmk1zdThRhgJt/x3ks6f30s3oE3H1mgIEU33XyQjEf8gsf6DxC7NPX8Y1SsNWjUjL/ywLnnbQ==", + "requires": { + "good-listener": "^1.2.2", + "select": "^1.1.2", + "tiny-emitter": "^2.0.0" + } + }, "clipboardy": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-2.3.0.tgz", @@ -73977,6 +74052,11 @@ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, + "delegate": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz", + "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==" + }, "delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", @@ -81674,6 +81754,14 @@ "minimatch": "~3.0.2" } }, + "good-listener": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz", + "integrity": "sha1-1TswzfkxPf+33JoNR3CWqm0UXFA=", + "requires": { + "delegate": "^3.1.2" + } + }, "google-protobuf": { "version": "3.17.3", "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.17.3.tgz", @@ -94695,6 +94783,16 @@ "prop-types": "^15.7.2" } }, + "react-clipboard.js": { + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/react-clipboard.js/-/react-clipboard.js-2.0.16.tgz", + "integrity": "sha512-COwmnbrRbl8y4f/SjtonnJTeBRD03YzsHBL5on8iL/uyjERsMkKC7djtfmns7iRAbzadn/84MdpaqaQ3ITP47g==", + "requires": { + "@types/clipboard": "^2.0.1", + "clipboard": "^2.0.0", + "prop-types": "^15.5.0" + } + }, "react-data-table-component": { "version": "6.11.7", "resolved": "https://registry.npmjs.org/react-data-table-component/-/react-data-table-component-6.11.7.tgz", @@ -96908,6 +97006,11 @@ "commander": "^2.8.1" } }, + "select": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz", + "integrity": "sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0=" + }, "select-hose": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", @@ -100004,6 +100107,11 @@ "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=" }, + "tiny-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", + "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==" + }, "tiny-queue": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/tiny-queue/-/tiny-queue-0.2.1.tgz", diff --git a/package.json b/package.json index 00d7cac9c..7bfab8799 100644 --- a/package.json +++ b/package.json @@ -70,6 +70,7 @@ "query-string": "^7.0.0", "react": "^17.0.2", "react-chartjs-2": "^2.11.2", + "react-clipboard.js": "^2.0.16", "react-data-table-component": "^6.11.7", "react-dom": "^17.0.2", "react-dotdotdot": "^1.3.1", diff --git a/src/components/atoms/Copy.module.css b/src/components/atoms/Copy.module.css new file mode 100644 index 000000000..ebc157ee4 --- /dev/null +++ b/src/components/atoms/Copy.module.css @@ -0,0 +1,35 @@ +.button { + display: inline-block; + margin: 0; + border: 0; + box-shadow: none; + background: none; + padding: 0; + cursor: pointer; + position: relative; +} + +.icon { + fill: var(--color-secondary); + transition: 0.15s ease-out; + width: 10px; + height: 10px; +} + +.button:hover .icon { + fill: var(--font-color-text); +} + +.copied .icon, +.button.copied:hover .icon { + fill: var(--brand-alert-green); +} + +.copied::after { + content: 'Copied!'; + position: absolute; + top: -150%; + left: -100%; + font-size: var(--font-size-mini); + color: var(--brand-alert-green); +} diff --git a/src/components/atoms/Copy.tsx b/src/components/atoms/Copy.tsx new file mode 100644 index 000000000..d0693963f --- /dev/null +++ b/src/components/atoms/Copy.tsx @@ -0,0 +1,33 @@ +import React, { ReactElement, useEffect, useState } from 'react' +import loadable from '@loadable/component' +import styles from './Copy.module.css' +import { ReactComponent as IconCopy } from '../../images/copy.svg' + +// lazy load when needed only, as library is a bit big +const Clipboard = loadable(() => import('react-clipboard.js')) + +export default function Copy({ text }: { text: string }): ReactElement { + const [isCopied, setIsCopied] = useState(false) + + // Clear copy success style after 5 sec. + useEffect(() => { + if (!isCopied) return + + const timeout = setTimeout(() => { + setIsCopied(false) + }, 5000) + + return () => clearTimeout(timeout) + }, [isCopied]) + + return ( + setIsCopied(true)} + className={`${styles.button} ${isCopied ? styles.copied : ''}`} + > + + + ) +} diff --git a/src/components/atoms/Publisher/ProfileDetails.module.css b/src/components/atoms/Publisher/ProfileDetails.module.css deleted file mode 100644 index a0e85bea5..000000000 --- a/src/components/atoms/Publisher/ProfileDetails.module.css +++ /dev/null @@ -1,72 +0,0 @@ -.profile { - background: var(--background-highlight); - border-radius: var(--border-radius); - padding: calc(var(--spacer) / 2); - margin-bottom: calc(var(--spacer) / 4); -} - -@media (min-width: 40rem) { - .profile { - margin: calc(var(--spacer) / 8); - margin-bottom: calc(var(--spacer) / 4); - } -} - -.profile p { - margin-bottom: calc(var(--spacer) / 4); -} - -.profile code { - padding: 0; - color: var(--color-secondary); - font-size: var(--font-size-mini); - overflow-wrap: break-word; - word-wrap: break-word; - word-break: break-word; -} - -.header { - margin-bottom: calc(var(--spacer) / 4); - text-align: center; -} - -.header::after { - content: ''; - display: block; - margin: calc(var(--spacer) / 2) auto; - width: 20%; - height: 2px; - background: var(--border-color); -} - -.image { - width: 48px; - height: 48px; - border-radius: 50%; - overflow: hidden; - display: inline-block; - margin-bottom: calc(var(--spacer) / 4); - border: 1px solid var(--border-color); - box-shadow: 0 6px 17px 0 var(--box-shadow-color); -} - -.title { - font-size: var(--font-size-base); - margin-bottom: 0; -} - -.description { - font-size: var(--font-size-small); -} - -.profile p:last-child { - margin-bottom: 0; -} - -.meta { - color: var(--color-secondary); - font-size: var(--font-size-mini); - text-align: right; - margin-left: calc(var(--spacer) / 4); - margin-right: calc(var(--spacer) / 4); -} diff --git a/src/components/atoms/Publisher/ProfileDetails.tsx b/src/components/atoms/Publisher/ProfileDetails.tsx deleted file mode 100644 index 7f4be381b..000000000 --- a/src/components/atoms/Publisher/ProfileDetails.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import React, { ReactElement } from 'react' -import styles from './ProfileDetails.module.css' -import { Profile } from '../../../models/Profile' -import ExplorerLink from '../ExplorerLink' -import PublisherLinks from './PublisherLinks' - -export default function ProfileDetails({ - profile, - networkId, - account -}: { - profile: Profile - networkId: number - account: string -}): ReactElement { - return ( - <> -
-
- {profile?.image && ( -
- -
- )} -

- {profile?.emoji} {profile?.name} -

- - - {account} - -
- - {profile?.description && ( -

{profile?.description}

- )} - -
-
- Profile data from{' '} - - 3Box Hub - -
- - ) -} diff --git a/src/components/atoms/Publisher/index.module.css b/src/components/atoms/Publisher/index.module.css index 71a0287b8..93592f599 100644 --- a/src/components/atoms/Publisher/index.module.css +++ b/src/components/atoms/Publisher/index.module.css @@ -8,40 +8,9 @@ } } -.links { - display: inline; -} - -.links a, -.links span { - margin-left: calc(var(--spacer) / 3); - font-size: var(--font-size-mini); -} - -.links a:first-child, -.links span:first-child { - margin-left: 0; -} - -.links a:hover, -.links a:focus { - color: var(--brand-pink); -} - .linksExternal { width: 6px; height: 6px; display: inline-block; fill: var(--color-secondary); } - -.detailsTrigger { - cursor: help; -} - -.detailsTrigger svg { - width: 10px; - height: 10px; - position: relative; - bottom: -1px; -} diff --git a/src/components/atoms/Publisher/index.tsx b/src/components/atoms/Publisher/index.tsx index 8fac6942c..38934bf90 100644 --- a/src/components/atoms/Publisher/index.tsx +++ b/src/components/atoms/Publisher/index.tsx @@ -1,15 +1,11 @@ import React, { ReactElement, useEffect, useState } from 'react' import styles from './index.module.css' import classNames from 'classnames/bind' -import Tooltip from '../Tooltip' import { Profile } from '../../../models/Profile' import { Link } from 'gatsby' import get3BoxProfile from '../../../utils/profile' -import ExplorerLink from '../ExplorerLink' import { accountTruncate } from '../../../utils/web3' import axios from 'axios' -import { ReactComponent as Info } from '../../../images/info.svg' -import ProfileDetails from './ProfileDetails' import Add from './Add' import { useWeb3 } from '../../../providers/Web3' @@ -62,35 +58,10 @@ export default function Publisher({ name ) : ( <> - + {name} -
- {' — '} - {profile && ( - - } - > - - Profile - - - )} - {showAdd && } - - Explorer - -
+ {showAdd && } )} diff --git a/src/components/atoms/Tabs.module.css b/src/components/atoms/Tabs.module.css index 3d76fc0d6..f138f9458 100644 --- a/src/components/atoms/Tabs.module.css +++ b/src/components/atoms/Tabs.module.css @@ -1,8 +1,7 @@ .tabList { text-align: center; border-bottom: 1px solid var(--border-color); - padding-top: calc(var(--spacer) / 2); - padding-bottom: calc(var(--spacer) / 2); + padding: calc(var(--spacer) / 2); } .tab { @@ -36,5 +35,11 @@ } .tabContent { - padding: var(--spacer); + padding: calc(var(--spacer) / 2); +} + +@media (min-width: 40rem) { + .tabContent { + padding: var(--spacer); + } } diff --git a/src/components/molecules/NumberUnit.module.css b/src/components/molecules/NumberUnit.module.css new file mode 100644 index 000000000..13fb622f4 --- /dev/null +++ b/src/components/molecules/NumberUnit.module.css @@ -0,0 +1,48 @@ +.number, +.number * { + font-weight: var(--font-weight-bold); + font-size: var(--font-size-h4); + color: var(--font-color-heading); +} + +.number { + white-space: nowrap; + display: inline-flex; + align-items: center; + line-height: 1; +} + +.number.small, +.number.small * { + font-size: var(--font-size-h5); +} + +.label { + display: block; +} + +.number svg { + width: var(--font-size-large); + height: var(--font-size-large); + margin-right: calc(var(--spacer) / 4); + stroke: currentColor; +} + +.unit { + font-size: var(--font-size-small); + color: var(--color-secondary); +} + +.unit a { + color: var(--color-secondary); + display: block; + padding: 0.5rem 1rem; + border: 0.1rem solid transparent; + border-radius: 0.2rem; +} + +.unit a:hover, +.unit a:focus { + background: var(--brand-white); + border: 0.1rem solid var(--brand-pink); +} diff --git a/src/components/molecules/NumberUnit.tsx b/src/components/molecules/NumberUnit.tsx new file mode 100644 index 000000000..72ffc7daa --- /dev/null +++ b/src/components/molecules/NumberUnit.tsx @@ -0,0 +1,45 @@ +import React, { ReactElement } from 'react' +import styles from './NumberUnit.module.css' + +interface NumberInnerProps { + label: string + value: number | string | Element | ReactElement + small?: boolean + icon?: Element | ReactElement +} + +interface NumberUnitProps extends NumberInnerProps { + link?: string + linkTooltip?: string +} + +const NumberInner = ({ small, label, value, icon }: NumberInnerProps) => ( + <> +
+ {icon && icon} + {value} +
+ {label} + +) + +export default function NumberUnit({ + link, + linkTooltip, + small, + label, + value, + icon +}: NumberUnitProps): ReactElement { + return ( +
+ {link ? ( + + + + ) : ( + + )} +
+ ) +} diff --git a/src/components/organisms/AssetActions/Compute/index.tsx b/src/components/organisms/AssetActions/Compute/index.tsx index 448e724ef..7984835d7 100644 --- a/src/components/organisms/AssetActions/Compute/index.tsx +++ b/src/components/organisms/AssetActions/Compute/index.tsx @@ -39,10 +39,10 @@ import { AssetSelectionAsset } from '../../../molecules/FormFields/AssetSelectio import AlgorithmDatasetsListForCompute from '../../AssetContent/AlgorithmDatasetsListForCompute' import { getPreviousOrders, getPrice } from '../../../../utils/subgraph' import AssetActionHistoryTable from '../../AssetActionHistoryTable' -import ComputeJobs from '../../../pages/Account/History/ComputeJobs' +import ComputeJobs from '../../../pages/Profile/History/ComputeJobs' const SuccessAction = () => ( - ) diff --git a/src/components/pages/Account/index.tsx b/src/components/pages/Account/index.tsx deleted file mode 100644 index 954599e35..000000000 --- a/src/components/pages/Account/index.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import React, { ReactElement } from 'react' -import HistoryPage from './History' -import { useWeb3 } from '../../../providers/Web3' - -export default function AccountPage({ - accountIdentifier -}: { - accountIdentifier: string -}): ReactElement { - const { accountId } = useWeb3() - if (!accountIdentifier) accountIdentifier = accountId - - return ( -
- {accountIdentifier ? ( -

WIP Account metadata header for user: {accountIdentifier}

- ) : ( -

Please connect your Web3 wallet.

- )} - -
- ) -} diff --git a/src/components/pages/Profile/Account.module.css b/src/components/pages/Profile/Account.module.css new file mode 100644 index 000000000..678f3c716 --- /dev/null +++ b/src/components/pages/Profile/Account.module.css @@ -0,0 +1,62 @@ +.account { + text-align: center; +} + +.account p { + margin: 0; +} + +@media (min-width: 40rem) { + .account { + text-align: left; + display: grid; + grid-template-columns: 0.2fr 1.8fr; + gap: calc(var(--spacer) / 2); + align-items: center; + } +} + +.imageWrap { + width: 96px; + height: 96px; + border-radius: 50%; + overflow: hidden; + margin-bottom: calc(var(--spacer) / 4); + border: 1px solid var(--border-color); + box-shadow: 0 6px 17px 0 var(--box-shadow-color); +} + +.image { + max-width: unset; +} + +.name { + font-size: var(--font-size-h3); + margin-bottom: calc(var(--spacer) / 8); +} + +.accountId { + display: block; + font-size: var(--font-size-small); + color: var(--color-secondary); + word-wrap: break-word; + white-space: pre-wrap; + padding: 0; + margin: 0; +} + +.explorer { + font-size: var(--font-size-mini); + margin-right: calc(var(--spacer) / 1.5); + display: inline-flex; + align-items: center; +} + +.explorer svg:first-child { + width: var(--font-size-mini); + height: var(--font-size-mini); +} + +.explorer svg:last-child { + margin-left: calc(var(--spacer) / 12); +} diff --git a/src/components/pages/Profile/Account.tsx b/src/components/pages/Profile/Account.tsx new file mode 100644 index 000000000..91bfccb6a --- /dev/null +++ b/src/components/pages/Profile/Account.tsx @@ -0,0 +1,75 @@ +import { toDataUrl } from 'ethereum-blockies' +import React, { ReactElement } from 'react' +import { useUserPreferences } from '../../../providers/UserPreferences' +import { accountTruncate } from '../../../utils/web3' +import ExplorerLink from '../../atoms/ExplorerLink' +import NetworkName from '../../atoms/NetworkName' +import jellyfish from '@oceanprotocol/art/creatures/jellyfish/jellyfish-grid.svg' +import styles from './Account.module.css' +import Copy from '../../atoms/Copy' + +const Blockies = ({ account }: { account: string | undefined }) => { + if (!account) return null + + const blockies = toDataUrl(account) + + return ( + + ) +} + +export default function Account({ + name, + image, + accountId +}: { + name: string + image: string + accountId: string +}): ReactElement { + const { chainIds } = useUserPreferences() + + return ( +
+
+ {image ? ( + + ) : accountId ? ( + + ) : ( + + )} +
+ +
+

{name || accountTruncate(accountId)}

+ + {accountId} + +

+ {accountId && + chainIds.map((value) => ( + + + + ))} +

+
+
+ ) +} diff --git a/src/components/pages/Profile/Header.module.css b/src/components/pages/Profile/Header.module.css new file mode 100644 index 000000000..98bf59bca --- /dev/null +++ b/src/components/pages/Profile/Header.module.css @@ -0,0 +1,54 @@ +.grid { + composes: box from '../../atoms/Box.module.css'; + background: var(--background-body-transparent); + backdrop-filter: blur(3px); + position: relative; +} + +.description { + color: var(--color-secondary); + font-size: var(--font-size-small); + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; + margin-top: var(--spacer); +} + +.description p:last-child { + margin-bottom: 0; +} + +@media (min-width: 50rem) { + .grid { + display: grid; + gap: var(--spacer); + /* lazy golden ratio */ + grid-template-columns: 1.618fr 1fr; + } + + .description { + margin-top: calc(var(--spacer) / 2); + -webkit-line-clamp: 7 !important; + } +} + +.publisherLinks { + margin-top: calc(var(--spacer) / 2); + margin-bottom: calc(var(--spacer) / 2); +} + +.more { + font-size: var(--font-size-mini); + margin-left: calc(var(--spacer) / 8); + cursor: pointer; +} + +.meta { + color: var(--color-secondary); + font-size: var(--font-size-mini); + position: absolute; + right: calc(var(--spacer) / 3); + bottom: calc(var(--spacer) / 6); +} diff --git a/src/components/pages/Profile/Header.tsx b/src/components/pages/Profile/Header.tsx new file mode 100644 index 000000000..c697bb387 --- /dev/null +++ b/src/components/pages/Profile/Header.tsx @@ -0,0 +1,109 @@ +import React, { ReactElement, useEffect, useState } from 'react' +import get3BoxProfile from '../../../utils/profile' +import { ProfileLink } from '../../../models/Profile' +import { accountTruncate } from '../../../utils/web3' +import axios from 'axios' +import PublisherLinks from './PublisherLinks' +import Markdown from '../../atoms/Markdown' +import Stats from './Stats' +import Account from './Account' +import styles from './Header.module.css' + +const isDescriptionTextClamped = () => { + const el = document.getElementById('description') + if (el) return el.scrollHeight > el.clientHeight +} + +export default function AccountHeader({ + accountId +}: { + accountId: string +}): ReactElement { + const [image, setImage] = useState() + const [name, setName] = useState(accountTruncate(accountId)) + const [description, setDescription] = useState() + const [links, setLinks] = useState() + const [isShowMore, setIsShowMore] = useState(false) + + const toogleShowMore = () => { + setIsShowMore(!isShowMore) + } + + useEffect(() => { + if (!accountId) { + setName(null) + setDescription(null) + setImage(null) + setLinks([]) + return + } + + const source = axios.CancelToken.source() + + async function getInfoFrom3Box() { + const profile = await get3BoxProfile(accountId, source.token) + if (profile) { + const { name, emoji, description, image, links } = profile + setName(`${emoji || ''} ${name || accountTruncate(accountId)}`) + setDescription(description || null) + setImage(image || null) + setLinks(links || []) + } else { + setName(null) + setDescription(null) + setImage(null) + setLinks([]) + } + } + getInfoFrom3Box() + + return () => { + source.cancel() + } + }, [accountId]) + + return ( +
+
+ + +
+ +
+ + {isDescriptionTextClamped() ? ( + + + Read more on 3box + + + ) : ( + '' + )} + {links?.length > 0 && ( + + )} +
+
+ Profile data from{' '} + + 3Box Hub + +
+
+ ) +} diff --git a/src/components/pages/Account/History/ComputeJobs/Details.module.css b/src/components/pages/Profile/History/ComputeJobs/Details.module.css similarity index 100% rename from src/components/pages/Account/History/ComputeJobs/Details.module.css rename to src/components/pages/Profile/History/ComputeJobs/Details.module.css diff --git a/src/components/pages/Account/History/ComputeJobs/Details.tsx b/src/components/pages/Profile/History/ComputeJobs/Details.tsx similarity index 98% rename from src/components/pages/Account/History/ComputeJobs/Details.tsx rename to src/components/pages/Profile/History/ComputeJobs/Details.tsx index ace32cf41..3620b2f99 100644 --- a/src/components/pages/Account/History/ComputeJobs/Details.tsx +++ b/src/components/pages/Profile/History/ComputeJobs/Details.tsx @@ -7,7 +7,6 @@ import Modal from '../../../../atoms/Modal' import MetaItem from '../../../../organisms/AssetContent/MetaItem' import { ReactComponent as External } from '../../../../../images/external.svg' import { retrieveDDO } from '../../../../../utils/aquarius' -import { useOcean } from '../../../../../providers/Ocean' import Results from './Results' import styles from './Details.module.css' import { useSiteMetadata } from '../../../../../hooks/useSiteMetadata' diff --git a/src/components/pages/Account/History/ComputeJobs/Results.module.css b/src/components/pages/Profile/History/ComputeJobs/Results.module.css similarity index 100% rename from src/components/pages/Account/History/ComputeJobs/Results.module.css rename to src/components/pages/Profile/History/ComputeJobs/Results.module.css diff --git a/src/components/pages/Account/History/ComputeJobs/Results.tsx b/src/components/pages/Profile/History/ComputeJobs/Results.tsx similarity index 100% rename from src/components/pages/Account/History/ComputeJobs/Results.tsx rename to src/components/pages/Profile/History/ComputeJobs/Results.tsx diff --git a/src/components/pages/Account/History/ComputeJobs/index.module.css b/src/components/pages/Profile/History/ComputeJobs/index.module.css similarity index 100% rename from src/components/pages/Account/History/ComputeJobs/index.module.css rename to src/components/pages/Profile/History/ComputeJobs/index.module.css diff --git a/src/components/pages/Account/History/ComputeJobs/index.tsx b/src/components/pages/Profile/History/ComputeJobs/index.tsx similarity index 98% rename from src/components/pages/Account/History/ComputeJobs/index.tsx rename to src/components/pages/Profile/History/ComputeJobs/index.tsx index b3f549f24..5dad65cf0 100644 --- a/src/components/pages/Account/History/ComputeJobs/index.tsx +++ b/src/components/pages/Profile/History/ComputeJobs/index.tsx @@ -22,10 +22,7 @@ import styles from './index.module.css' import { useUserPreferences } from '../../../../../providers/UserPreferences' import { getOceanConfig } from '../../../../../utils/ocean' import { fetchDataForMultipleChains } from '../../../../../utils/subgraph' -import { - OrdersData_tokenOrders as OrdersData, - OrdersData_tokenOrders_datatokenId as OrdersDatatoken -} from '../../../../../@types/apollo/OrdersData' +import { OrdersData_tokenOrders_datatokenId as OrdersDatatoken } from '../../../../../@types/apollo/OrdersData' import NetworkName from '../../../../atoms/NetworkName' const getComputeOrders = gql` diff --git a/src/components/pages/Account/History/Downloads.tsx b/src/components/pages/Profile/History/Downloads.tsx similarity index 98% rename from src/components/pages/Account/History/Downloads.tsx rename to src/components/pages/Profile/History/Downloads.tsx index aa8f5e81c..0d206b631 100644 --- a/src/components/pages/Account/History/Downloads.tsx +++ b/src/components/pages/Profile/History/Downloads.tsx @@ -4,7 +4,6 @@ import { gql } from 'urql' import Time from '../../../atoms/Time' import web3 from 'web3' import AssetTitle from '../../../molecules/AssetListTitle' -import { useWeb3 } from '../../../../providers/Web3' import axios from 'axios' import { retrieveDDO } from '../../../../utils/aquarius' import { Logger } from '@oceanprotocol/lib' diff --git a/src/components/pages/Account/History/PoolShares.module.css b/src/components/pages/Profile/History/PoolShares.module.css similarity index 100% rename from src/components/pages/Account/History/PoolShares.module.css rename to src/components/pages/Profile/History/PoolShares.module.css diff --git a/src/components/pages/Account/History/PoolShares.tsx b/src/components/pages/Profile/History/PoolShares.tsx similarity index 92% rename from src/components/pages/Account/History/PoolShares.tsx rename to src/components/pages/Profile/History/PoolShares.tsx index 39f03db93..de24051d7 100644 --- a/src/components/pages/Account/History/PoolShares.tsx +++ b/src/components/pages/Profile/History/PoolShares.tsx @@ -5,15 +5,16 @@ import styles from './PoolShares.module.css' import AssetTitle from '../../../molecules/AssetListTitle' import { gql } from 'urql' import { - PoolShares as PoolSharesList, PoolShares_poolShares as PoolShare, PoolShares_poolShares_poolId_tokens as PoolSharePoolIdTokens } from '../../../../@types/apollo/PoolShares' import web3 from 'web3' import Token from '../../../organisms/AssetActions/Pool/Token' -import { useWeb3 } from '../../../../providers/Web3' import { useUserPreferences } from '../../../../providers/UserPreferences' -import { fetchDataForMultipleChains } from '../../../../utils/subgraph' +import { + fetchDataForMultipleChains, + calculateUserLiquidity +} from '../../../../utils/subgraph' import NetworkName from '../../../atoms/NetworkName' import axios from 'axios' import { retrieveDDO } from '../../../../utils/aquarius' @@ -59,17 +60,6 @@ interface Asset { createTime: number } -function calculateUserLiquidity(poolShare: PoolShare) { - const ocean = - (poolShare.balance / poolShare.poolId.totalShares) * - poolShare.poolId.oceanReserve - const datatokens = - (poolShare.balance / poolShare.poolId.totalShares) * - poolShare.poolId.datatokenReserve - const totalLiquidity = ocean + datatokens * poolShare.poolId.consumePrice - return totalLiquidity -} - function findValidToken(tokens: PoolSharePoolIdTokens[]) { const symbol = tokens.find((token) => token.tokenId !== null) return symbol.tokenId.symbol diff --git a/src/components/pages/Account/History/PublishedList.tsx b/src/components/pages/Profile/History/PublishedList.tsx similarity index 96% rename from src/components/pages/Account/History/PublishedList.tsx rename to src/components/pages/Profile/History/PublishedList.tsx index c9f4cda3c..da7c42c6a 100644 --- a/src/components/pages/Account/History/PublishedList.tsx +++ b/src/components/pages/Profile/History/PublishedList.tsx @@ -1,6 +1,6 @@ import { Logger } from '@oceanprotocol/lib' import { QueryResult } from '@oceanprotocol/lib/dist/node/metadatacache/MetadataCache' -import React, { ReactElement, useEffect, useState, useReducer } from 'react' +import React, { ReactElement, useEffect, useState } from 'react' import AssetList from '../../../organisms/AssetList' import axios from 'axios' import { diff --git a/src/components/pages/Account/History/index.module.css b/src/components/pages/Profile/History/index.module.css similarity index 98% rename from src/components/pages/Account/History/index.module.css rename to src/components/pages/Profile/History/index.module.css index 7bfacd3ef..75db8b925 100644 --- a/src/components/pages/Account/History/index.module.css +++ b/src/components/pages/Profile/History/index.module.css @@ -16,7 +16,7 @@ } } -.content { +.tabs { margin-top: var(--spacer); background-color: var(--background-body); } diff --git a/src/components/pages/Account/History/index.tsx b/src/components/pages/Profile/History/index.tsx similarity index 83% rename from src/components/pages/Account/History/index.tsx rename to src/components/pages/Profile/History/index.tsx index 2a54e7b98..dc817fb50 100644 --- a/src/components/pages/Account/History/index.tsx +++ b/src/components/pages/Profile/History/index.tsx @@ -5,8 +5,8 @@ import PoolTransactions from '../../../molecules/PoolTransactions' import PublishedList from './PublishedList' import Downloads from './Downloads' import ComputeJobs from './ComputeJobs' +import { useLocation } from '@reach/router' import styles from './index.module.css' -import { useUserPreferences } from '../../../../providers/UserPreferences' import OceanProvider from '../../../../providers/Ocean' import { useWeb3 } from '../../../../providers/Web3' @@ -53,20 +53,17 @@ export default function HistoryPage({ }: { accountIdentifier: string }): ReactElement { - const { chainIds } = useUserPreferences() const { accountId } = useWeb3() - const url = new URL(window.location.href) + const location = useLocation() + + const url = new URL(location.href) const defaultTab = url.searchParams.get('defaultTab') const tabs = getTabs(accountIdentifier, accountId) + let defaultTabIndex = 0 defaultTab === 'ComputeJobs' ? (defaultTabIndex = 4) : (defaultTabIndex = 0) + return ( -
- -
+ ) } diff --git a/src/components/atoms/Publisher/PublisherLinks.module.css b/src/components/pages/Profile/PublisherLinks.module.css similarity index 69% rename from src/components/atoms/Publisher/PublisherLinks.module.css rename to src/components/pages/Profile/PublisherLinks.module.css index fb52e55ef..c24bb9f98 100644 --- a/src/components/atoms/Publisher/PublisherLinks.module.css +++ b/src/components/pages/Profile/PublisherLinks.module.css @@ -1,3 +1,7 @@ +.links { + width: 100%; +} + .links, .links a { font-size: var(--font-size-small); @@ -7,6 +11,7 @@ .links a { margin-left: calc(var(--spacer) / 3); color: inherit; + display: inline-block; } .links a:first-child { @@ -19,5 +24,5 @@ } .linksExternal { - composes: linksExternal from './index.module.css'; + composes: linksExternal from '../../atoms/Publisher/index.module.css'; } diff --git a/src/components/atoms/Publisher/PublisherLinks.tsx b/src/components/pages/Profile/PublisherLinks.tsx similarity index 80% rename from src/components/atoms/Publisher/PublisherLinks.tsx rename to src/components/pages/Profile/PublisherLinks.tsx index 46ec0cba4..40b422a62 100644 --- a/src/components/atoms/Publisher/PublisherLinks.tsx +++ b/src/components/pages/Profile/PublisherLinks.tsx @@ -1,15 +1,25 @@ import React, { ReactElement } from 'react' -import styles from './PublisherLinks.module.css' +import classNames from 'classnames/bind' import { ProfileLink } from '../../../models/Profile' import { ReactComponent as External } from '../../../images/external.svg' +import styles from './PublisherLinks.module.css' + +const cx = classNames.bind(styles) export default function PublisherLinks({ - links + links, + className }: { links: ProfileLink[] + className: string }): ReactElement { + const styleClasses = cx({ + links: true, + [className]: className + }) + return ( -
+
{' — '} {links?.map((link: ProfileLink) => { const href = diff --git a/src/components/pages/Profile/Stats.module.css b/src/components/pages/Profile/Stats.module.css new file mode 100644 index 000000000..8878438c8 --- /dev/null +++ b/src/components/pages/Profile/Stats.module.css @@ -0,0 +1,6 @@ +.stats { + display: grid; + gap: var(--spacer); + grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr)); + margin-top: calc(var(--spacer) / 2); +} diff --git a/src/components/pages/Profile/Stats.tsx b/src/components/pages/Profile/Stats.tsx new file mode 100644 index 000000000..fd43dccb4 --- /dev/null +++ b/src/components/pages/Profile/Stats.tsx @@ -0,0 +1,103 @@ +import { DDO, Logger } from '@oceanprotocol/lib' +import React, { useEffect, useState } from 'react' +import { ReactElement } from 'react-markdown' +import { useUserPreferences } from '../../../providers/UserPreferences' +import { + getAccountLiquidityInOwnAssets, + getAccountNumberOfOrders, + getAssetsBestPrices, + UserTVL +} from '../../../utils/subgraph' +import Conversion from '../../atoms/Price/Conversion' +import NumberUnit from '../../molecules/NumberUnit' +import styles from './Stats.module.css' +import { + queryMetadata, + transformChainIdsListToQuery +} from '../../../utils/aquarius' +import axios from 'axios' + +export default function Stats({ + accountId +}: { + accountId: string +}): ReactElement { + const { chainIds } = useUserPreferences() + + const [publishedAssets, setPublishedAssets] = useState() + const [numberOfAssets, setNumberOfAssets] = useState(0) + const [sold, setSold] = useState(0) + const [tvl, setTvl] = useState() + + useEffect(() => { + if (!accountId) { + setNumberOfAssets(0) + setSold(0) + setTvl({ price: '0', oceanBalance: '0' }) + return + } + + async function getPublished() { + const queryPublishedAssets = { + query: { + query_string: { + query: `(publicKey.owner:${accountId}) AND (${transformChainIdsListToQuery( + chainIds + )})` + } + } + } + try { + const source = axios.CancelToken.source() + const result = await queryMetadata(queryPublishedAssets, source.token) + setPublishedAssets(result.results) + setNumberOfAssets(result.totalResults) + } catch (error) { + Logger.error(error.message) + } + } + getPublished() + + async function getAccountSoldValue() { + const nrOrders = await getAccountNumberOfOrders(accountId, chainIds) + setSold(nrOrders) + } + getAccountSoldValue() + }, [accountId, chainIds]) + + useEffect(() => { + if (!publishedAssets) return + + async function getAccountTVL() { + try { + const accountPoolAdresses: string[] = [] + const assetsPrices = await getAssetsBestPrices(publishedAssets) + for (const priceInfo of assetsPrices) { + if (priceInfo.price.type === 'pool') { + accountPoolAdresses.push(priceInfo.price.address.toLowerCase()) + } + } + const userTvl: UserTVL = await getAccountLiquidityInOwnAssets( + accountId, + chainIds, + accountPoolAdresses + ) + setTvl(userTvl) + } catch (error) { + Logger.error(error.message) + } + } + getAccountTVL() + }, [publishedAssets]) + + return ( +
+ + + } + /> +
+ ) +} diff --git a/src/components/pages/Profile/index.tsx b/src/components/pages/Profile/index.tsx new file mode 100644 index 000000000..d4eafd450 --- /dev/null +++ b/src/components/pages/Profile/index.tsx @@ -0,0 +1,16 @@ +import React, { ReactElement } from 'react' +import HistoryPage from './History' +import AccountHeader from './Header' + +export default function AccountPage({ + accountId +}: { + accountId: string +}): ReactElement { + return ( + <> + + + + ) +} diff --git a/src/components/templates/Page.tsx b/src/components/templates/Page.tsx index 1672d4dbf..b91717999 100644 --- a/src/components/templates/Page.tsx +++ b/src/components/templates/Page.tsx @@ -5,7 +5,7 @@ import Container from '../atoms/Container' export interface PageProps { children: ReactNode - title: string + title?: string uri: string description?: string noPageHeader?: boolean diff --git a/src/pages/account/index.tsx b/src/pages/account/index.tsx deleted file mode 100644 index f7c1fe9da..000000000 --- a/src/pages/account/index.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import React, { ReactElement, useState, useEffect } from 'react' -import Page from '../../components/templates/Page' -import { graphql, PageProps } from 'gatsby' -import AccountPage from '../../components/pages/Account' - -export default function PageGatsbyAccount(props: PageProps): ReactElement { - const content = (props.data as any).content.edges[0].node.childPagesJson - const { title } = content - const [accountId, setAccountId] = useState() - - useEffect(() => { - setAccountId(props.location.pathname.split('/')[2]) - }, [props.location.pathname]) - - return ( - - - - ) -} - -export const contentQuery = graphql` - query AccountPageQuery { - content: allFile(filter: { relativePath: { eq: "pages/account.json" } }) { - edges { - node { - childPagesJson { - title - description - } - } - } - } - } -` diff --git a/src/pages/profile/index.tsx b/src/pages/profile/index.tsx new file mode 100644 index 000000000..451a639ea --- /dev/null +++ b/src/pages/profile/index.tsx @@ -0,0 +1,39 @@ +import React, { ReactElement, useEffect, useState } from 'react' +import Page from '../../components/templates/Page' +import { graphql, PageProps } from 'gatsby' +import ProfilePage from '../../components/pages/Profile' +import { accountTruncate } from '../../utils/web3' +import { useWeb3 } from '../../providers/Web3' + +export default function PageGatsbyProfile(props: PageProps): ReactElement { + const { accountId } = useWeb3() + const [finalAccountId, setFinalAccountId] = useState() + + // Have accountId in path take over, if not present fall back to web3 + useEffect(() => { + const pathAccountId = props.location.pathname.split('/')[2] + const finalAccountId = pathAccountId || accountId + setFinalAccountId(finalAccountId) + }, [props.location.pathname, accountId]) + + return ( + + + + ) +} + +export const contentQuery = graphql` + query ProfilePageQuery { + content: allFile(filter: { relativePath: { eq: "pages/profile.json" } }) { + edges { + node { + childPagesJson { + title + description + } + } + } + } + } +` diff --git a/src/utils/subgraph.ts b/src/utils/subgraph.ts index 0d11cef74..53c7b16aa 100644 --- a/src/utils/subgraph.ts +++ b/src/utils/subgraph.ts @@ -20,6 +20,15 @@ import { HighestLiquidityAssets_pools as HighestLiquidityAssetsPools, HighestLiquidityAssets as HighestLiquidityGraphAssets } from '../@types/apollo/HighestLiquidityAssets' +import { + PoolShares as PoolSharesList, + PoolShares_poolShares as PoolShare +} from '../@types/apollo/PoolShares' + +export interface UserTVL { + price: string + oceanBalance: string +} export interface PriceList { [key: string]: string @@ -141,6 +150,43 @@ const HighestLiquidityAssets = gql` } ` +const TotalAccountOrders = gql` + query TotalAccountOrders($payer: String) { + tokenOrders(orderBy: id, where: { payer: $payer }) { + id + payer { + id + } + } + } +` +const UserSharesQuery = gql` + query UserSharesQuery($user: String, $pools: [String!]) { + poolShares(where: { userAddress: $user, poolId_in: $pools }) { + id + balance + userAddress { + id + } + poolId { + id + datatokenAddress + valueLocked + tokens { + tokenId { + symbol + } + } + oceanReserve + datatokenReserve + totalShares + consumePrice + spotPrice + createTime + } + } + } +` export function getSubgraphUri(chainId: number): string { const config = getOceanConfig(chainId) return config.subgraphUri @@ -497,3 +543,64 @@ export async function getHighestLiquidityDIDs( .replace(/(did:op:)/g, '0x') return [searchDids, didList.length] } + +export async function getAccountNumberOfOrders( + accountId: string, + chainIds: number[] +): Promise { + const queryVariables = { + payer: accountId.toLowerCase() + } + const results = await fetchDataForMultipleChains( + TotalAccountOrders, + queryVariables, + chainIds + ) + let numberOfOrders = 0 + for (const result of results) { + numberOfOrders += result.tokenOrders.length + } + return numberOfOrders +} + +export function calculateUserLiquidity(poolShare: PoolShare) { + const ocean = + (poolShare.balance / poolShare.poolId.totalShares) * + poolShare.poolId.oceanReserve + const datatokens = + (poolShare.balance / poolShare.poolId.totalShares) * + poolShare.poolId.datatokenReserve + const totalLiquidity = ocean + datatokens * poolShare.poolId.consumePrice + return totalLiquidity +} + +export async function getAccountLiquidityInOwnAssets( + accountId: string, + chainIds: number[], + pools: string[] +): Promise { + const queryVariables = { + user: accountId.toLowerCase(), + pools: pools + } + const results: PoolSharesList[] = await fetchDataForMultipleChains( + UserSharesQuery, + queryVariables, + chainIds + ) + let totalLiquidity = 0 + let totalOceanLiquidity = 0 + for (const result of results) { + for (const poolShare of result.poolShares) { + const userShare = poolShare.balance / poolShare.poolId.totalShares + const userBalance = userShare * poolShare.poolId.oceanReserve + totalOceanLiquidity += userBalance + const poolLiquidity = calculateUserLiquidity(poolShare) + totalLiquidity += poolLiquidity + } + } + return { + price: totalLiquidity.toString(), + oceanBalance: totalOceanLiquidity.toString() + } +} diff --git a/tests/unit/__fixtures__/siteMetadata.json b/tests/unit/__fixtures__/siteMetadata.json index fba721a67..a00cd86a2 100644 --- a/tests/unit/__fixtures__/siteMetadata.json +++ b/tests/unit/__fixtures__/siteMetadata.json @@ -23,8 +23,8 @@ "link": "/publish" }, { - "name": "Account", - "link": "/account" + "name": "Profile", + "link": "/profile" } ] }