1
0
mirror of https://github.com/oceanprotocol/market.git synced 2024-12-02 05:57:29 +01:00

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 <m@kretschmann.io>
This commit is contained in:
Bogdan Fazakas 2021-09-01 14:56:34 +03:00 committed by GitHub
parent 3112a10930
commit f8ffcbac75
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 894 additions and 292 deletions

View File

@ -1,2 +1,2 @@
/asset/* /asset/index.html 200 /asset/* /asset/index.html 200
/account/* /account/index.html 200 /profile/* /profile/index.html 200

View File

@ -12,8 +12,8 @@
"link": "/publish" "link": "/publish"
}, },
{ {
"name": "My Account", "name": "Profile",
"link": "/account" "link": "/profile"
} }
], ],
"warning": { "warning": {

View File

@ -34,14 +34,14 @@ exports.onCreatePage = async ({ page, actions }) => {
// page.matchPath is a special key that's used for matching pages // page.matchPath is a special key that's used for matching pages
// only on the client. // only on the client.
const handleClientSideOnlyAsset = page.path.match(/^\/asset/) const handleClientSideOnlyAsset = page.path.match(/^\/asset/)
const handleClientSideOnlyAccount = page.path.match(/^\/account/) const handleClientSideOnlyAccount = page.path.match(/^\/profile/)
if (handleClientSideOnlyAsset) { if (handleClientSideOnlyAsset) {
page.matchPath = '/asset/*' page.matchPath = '/asset/*'
// Update the page. // Update the page.
createPage(page) createPage(page)
} else if (handleClientSideOnlyAccount) { } else if (handleClientSideOnlyAccount) {
page.matchPath = '/account/*' page.matchPath = '/profile/*'
createPage(page) createPage(page)
} }
} }

108
package-lock.json generated
View File

@ -55,6 +55,7 @@
"query-string": "^7.0.0", "query-string": "^7.0.0",
"react": "^17.0.2", "react": "^17.0.2",
"react-chartjs-2": "^2.11.2", "react-chartjs-2": "^2.11.2",
"react-clipboard.js": "^2.0.16",
"react-data-table-component": "^6.11.7", "react-data-table-component": "^6.11.7",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-dotdotdot": "^1.3.1", "react-dotdotdot": "^1.3.1",
@ -10524,6 +10525,15 @@
"classnames": "*" "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": { "node_modules/@types/common-tags": {
"version": "1.8.0", "version": "1.8.0",
"resolved": "https://registry.npmjs.org/@types/common-tags/-/common-tags-1.8.0.tgz", "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", "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz",
"integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==" "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": { "node_modules/clipboardy": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-2.3.0.tgz", "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-2.3.0.tgz",
@ -19004,6 +19024,11 @@
"node": ">=0.4.0" "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": { "node_modules/delegates": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
@ -28778,6 +28803,14 @@
"node": ">= 0.10" "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": { "node_modules/google-protobuf": {
"version": "3.17.3", "version": "3.17.3",
"resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.17.3.tgz", "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" "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": { "node_modules/react-data-table-component": {
"version": "6.11.7", "version": "6.11.7",
"resolved": "https://registry.npmjs.org/react-data-table-component/-/react-data-table-component-6.11.7.tgz", "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" "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": { "node_modules/select-hose": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", "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", "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz",
"integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=" "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": { "node_modules/tiny-queue": {
"version": "0.2.1", "version": "0.2.1",
"resolved": "https://registry.npmjs.org/tiny-queue/-/tiny-queue-0.2.1.tgz", "resolved": "https://registry.npmjs.org/tiny-queue/-/tiny-queue-0.2.1.tgz",
@ -66972,6 +67029,14 @@
"classnames": "*" "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": { "@types/common-tags": {
"version": "1.8.0", "version": "1.8.0",
"resolved": "https://registry.npmjs.org/@types/common-tags/-/common-tags-1.8.0.tgz", "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", "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz",
"integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==" "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": { "clipboardy": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-2.3.0.tgz", "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", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" "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": { "delegates": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
@ -81674,6 +81754,14 @@
"minimatch": "~3.0.2" "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": { "google-protobuf": {
"version": "3.17.3", "version": "3.17.3",
"resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.17.3.tgz", "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.17.3.tgz",
@ -94695,6 +94783,16 @@
"prop-types": "^15.7.2" "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": { "react-data-table-component": {
"version": "6.11.7", "version": "6.11.7",
"resolved": "https://registry.npmjs.org/react-data-table-component/-/react-data-table-component-6.11.7.tgz", "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" "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": { "select-hose": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", "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", "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz",
"integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=" "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": { "tiny-queue": {
"version": "0.2.1", "version": "0.2.1",
"resolved": "https://registry.npmjs.org/tiny-queue/-/tiny-queue-0.2.1.tgz", "resolved": "https://registry.npmjs.org/tiny-queue/-/tiny-queue-0.2.1.tgz",

View File

@ -70,6 +70,7 @@
"query-string": "^7.0.0", "query-string": "^7.0.0",
"react": "^17.0.2", "react": "^17.0.2",
"react-chartjs-2": "^2.11.2", "react-chartjs-2": "^2.11.2",
"react-clipboard.js": "^2.0.16",
"react-data-table-component": "^6.11.7", "react-data-table-component": "^6.11.7",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-dotdotdot": "^1.3.1", "react-dotdotdot": "^1.3.1",

View File

@ -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);
}

View File

@ -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 (
<Clipboard
data-clipboard-text={text}
button-title="Copy to clipboard"
onSuccess={() => setIsCopied(true)}
className={`${styles.button} ${isCopied ? styles.copied : ''}`}
>
<IconCopy className={styles.icon} />
</Clipboard>
)
}

View File

@ -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);
}

View File

@ -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 (
<>
<div className={styles.profile}>
<header className={styles.header}>
{profile?.image && (
<div className={styles.image}>
<img src={profile.image} width="48" height="48" />
</div>
)}
<h3 className={styles.title}>
{profile?.emoji} {profile?.name}
</h3>
<ExplorerLink networkId={networkId} path={`address/${account}`}>
<code>{account}</code>
</ExplorerLink>
</header>
{profile?.description && (
<p className={styles.description}>{profile?.description}</p>
)}
<PublisherLinks links={profile?.links} />
</div>
<div className={styles.meta}>
Profile data from{' '}
<a
href={`https://www.3box.io/${account}`}
target="_blank"
rel="noreferrer"
>
3Box Hub
</a>
</div>
</>
)
}

View File

@ -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 { .linksExternal {
width: 6px; width: 6px;
height: 6px; height: 6px;
display: inline-block; display: inline-block;
fill: var(--color-secondary); fill: var(--color-secondary);
} }
.detailsTrigger {
cursor: help;
}
.detailsTrigger svg {
width: 10px;
height: 10px;
position: relative;
bottom: -1px;
}

View File

@ -1,15 +1,11 @@
import React, { ReactElement, useEffect, useState } from 'react' import React, { ReactElement, useEffect, useState } from 'react'
import styles from './index.module.css' import styles from './index.module.css'
import classNames from 'classnames/bind' import classNames from 'classnames/bind'
import Tooltip from '../Tooltip'
import { Profile } from '../../../models/Profile' import { Profile } from '../../../models/Profile'
import { Link } from 'gatsby' import { Link } from 'gatsby'
import get3BoxProfile from '../../../utils/profile' import get3BoxProfile from '../../../utils/profile'
import ExplorerLink from '../ExplorerLink'
import { accountTruncate } from '../../../utils/web3' import { accountTruncate } from '../../../utils/web3'
import axios from 'axios' import axios from 'axios'
import { ReactComponent as Info } from '../../../images/info.svg'
import ProfileDetails from './ProfileDetails'
import Add from './Add' import Add from './Add'
import { useWeb3 } from '../../../providers/Web3' import { useWeb3 } from '../../../providers/Web3'
@ -62,35 +58,10 @@ export default function Publisher({
name name
) : ( ) : (
<> <>
<Link <Link to={`/profile/${account}`} title="Show profile page.">
to={`/search?sort=created&sortOrder=desc&text=${account}`}
title="Show all data sets created by this account."
>
{name} {name}
</Link> </Link>
<div className={styles.links}> {showAdd && <Add />}
{' — '}
{profile && (
<Tooltip
placement="bottom"
content={
<ProfileDetails
profile={profile}
networkId={networkId}
account={account}
/>
}
>
<span className={styles.detailsTrigger}>
Profile <Info className={styles.linksExternal} />
</span>
</Tooltip>
)}
{showAdd && <Add />}
<ExplorerLink networkId={networkId} path={`address/${account}`}>
Explorer
</ExplorerLink>
</div>
</> </>
)} )}
</div> </div>

View File

@ -1,8 +1,7 @@
.tabList { .tabList {
text-align: center; text-align: center;
border-bottom: 1px solid var(--border-color); border-bottom: 1px solid var(--border-color);
padding-top: calc(var(--spacer) / 2); padding: calc(var(--spacer) / 2);
padding-bottom: calc(var(--spacer) / 2);
} }
.tab { .tab {
@ -36,5 +35,11 @@
} }
.tabContent { .tabContent {
padding: var(--spacer); padding: calc(var(--spacer) / 2);
}
@media (min-width: 40rem) {
.tabContent {
padding: var(--spacer);
}
} }

View File

@ -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);
}

View File

@ -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) => (
<>
<div className={`${styles.number} ${small && styles.small}`}>
{icon && icon}
{value}
</div>
<span className={styles.label}>{label}</span>
</>
)
export default function NumberUnit({
link,
linkTooltip,
small,
label,
value,
icon
}: NumberUnitProps): ReactElement {
return (
<div className={styles.unit}>
{link ? (
<a href={link} title={linkTooltip}>
<NumberInner small={small} label={label} value={value} icon={icon} />
</a>
) : (
<NumberInner small={small} label={label} value={value} icon={icon} />
)}
</div>
)
}

View File

@ -39,10 +39,10 @@ import { AssetSelectionAsset } from '../../../molecules/FormFields/AssetSelectio
import AlgorithmDatasetsListForCompute from '../../AssetContent/AlgorithmDatasetsListForCompute' import AlgorithmDatasetsListForCompute from '../../AssetContent/AlgorithmDatasetsListForCompute'
import { getPreviousOrders, getPrice } from '../../../../utils/subgraph' import { getPreviousOrders, getPrice } from '../../../../utils/subgraph'
import AssetActionHistoryTable from '../../AssetActionHistoryTable' import AssetActionHistoryTable from '../../AssetActionHistoryTable'
import ComputeJobs from '../../../pages/Account/History/ComputeJobs' import ComputeJobs from '../../../pages/Profile/History/ComputeJobs'
const SuccessAction = () => ( const SuccessAction = () => (
<Button style="text" to="/account?defaultTab=ComputeJobs" size="small"> <Button style="text" to="/profile?defaultTab=ComputeJobs" size="small">
Go to history Go to history
</Button> </Button>
) )

View File

@ -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 (
<article>
{accountIdentifier ? (
<p>WIP Account metadata header for user: {accountIdentifier}</p>
) : (
<p>Please connect your Web3 wallet.</p>
)}
<HistoryPage accountIdentifier={accountIdentifier} />
</article>
)
}

View File

@ -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);
}

View File

@ -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 (
<img
className={styles.image}
src={blockies}
alt="Blockies"
aria-hidden="true"
/>
)
}
export default function Account({
name,
image,
accountId
}: {
name: string
image: string
accountId: string
}): ReactElement {
const { chainIds } = useUserPreferences()
return (
<div className={styles.account}>
<figure className={styles.imageWrap}>
{image ? (
<img src={image} className={styles.image} width="96" height="96" />
) : accountId ? (
<Blockies account={accountId} />
) : (
<img
src={jellyfish}
className={styles.image}
width="96"
height="96"
/>
)}
</figure>
<div>
<h3 className={styles.name}>{name || accountTruncate(accountId)}</h3>
<code className={styles.accountId}>
{accountId} <Copy text={accountId} />
</code>
<p>
{accountId &&
chainIds.map((value) => (
<ExplorerLink
className={styles.explorer}
networkId={value}
path={`address/${accountId}`}
key={value}
>
<NetworkName networkId={value} />
</ExplorerLink>
))}
</p>
</div>
</div>
)
}

View File

@ -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);
}

View File

@ -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<string>()
const [name, setName] = useState(accountTruncate(accountId))
const [description, setDescription] = useState<string>()
const [links, setLinks] = useState<ProfileLink[]>()
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 (
<div className={styles.grid}>
<div>
<Account accountId={accountId} image={image} name={name} />
<Stats accountId={accountId} />
</div>
<div>
<Markdown
text={
description ||
'No description found on [3box](https://3box.io/login).'
}
className={styles.description}
/>
{isDescriptionTextClamped() ? (
<span className={styles.more} onClick={toogleShowMore}>
<a
href={`https://www.3box.io/${accountId}`}
target="_blank"
rel="noreferrer"
>
Read more on 3box
</a>
</span>
) : (
''
)}
{links?.length > 0 && (
<PublisherLinks links={links} className={styles.publisherLinks} />
)}
</div>
<div className={styles.meta}>
Profile data from{' '}
<a
href={`https://www.3box.io/${accountId}`}
target="_blank"
rel="noreferrer"
>
3Box Hub
</a>
</div>
</div>
)
}

View File

@ -7,7 +7,6 @@ import Modal from '../../../../atoms/Modal'
import MetaItem from '../../../../organisms/AssetContent/MetaItem' import MetaItem from '../../../../organisms/AssetContent/MetaItem'
import { ReactComponent as External } from '../../../../../images/external.svg' import { ReactComponent as External } from '../../../../../images/external.svg'
import { retrieveDDO } from '../../../../../utils/aquarius' import { retrieveDDO } from '../../../../../utils/aquarius'
import { useOcean } from '../../../../../providers/Ocean'
import Results from './Results' import Results from './Results'
import styles from './Details.module.css' import styles from './Details.module.css'
import { useSiteMetadata } from '../../../../../hooks/useSiteMetadata' import { useSiteMetadata } from '../../../../../hooks/useSiteMetadata'

View File

@ -22,10 +22,7 @@ import styles from './index.module.css'
import { useUserPreferences } from '../../../../../providers/UserPreferences' import { useUserPreferences } from '../../../../../providers/UserPreferences'
import { getOceanConfig } from '../../../../../utils/ocean' import { getOceanConfig } from '../../../../../utils/ocean'
import { fetchDataForMultipleChains } from '../../../../../utils/subgraph' import { fetchDataForMultipleChains } from '../../../../../utils/subgraph'
import { import { OrdersData_tokenOrders_datatokenId as OrdersDatatoken } from '../../../../../@types/apollo/OrdersData'
OrdersData_tokenOrders as OrdersData,
OrdersData_tokenOrders_datatokenId as OrdersDatatoken
} from '../../../../../@types/apollo/OrdersData'
import NetworkName from '../../../../atoms/NetworkName' import NetworkName from '../../../../atoms/NetworkName'
const getComputeOrders = gql` const getComputeOrders = gql`

View File

@ -4,7 +4,6 @@ import { gql } from 'urql'
import Time from '../../../atoms/Time' import Time from '../../../atoms/Time'
import web3 from 'web3' import web3 from 'web3'
import AssetTitle from '../../../molecules/AssetListTitle' import AssetTitle from '../../../molecules/AssetListTitle'
import { useWeb3 } from '../../../../providers/Web3'
import axios from 'axios' import axios from 'axios'
import { retrieveDDO } from '../../../../utils/aquarius' import { retrieveDDO } from '../../../../utils/aquarius'
import { Logger } from '@oceanprotocol/lib' import { Logger } from '@oceanprotocol/lib'

View File

@ -5,15 +5,16 @@ import styles from './PoolShares.module.css'
import AssetTitle from '../../../molecules/AssetListTitle' import AssetTitle from '../../../molecules/AssetListTitle'
import { gql } from 'urql' import { gql } from 'urql'
import { import {
PoolShares as PoolSharesList,
PoolShares_poolShares as PoolShare, PoolShares_poolShares as PoolShare,
PoolShares_poolShares_poolId_tokens as PoolSharePoolIdTokens PoolShares_poolShares_poolId_tokens as PoolSharePoolIdTokens
} from '../../../../@types/apollo/PoolShares' } from '../../../../@types/apollo/PoolShares'
import web3 from 'web3' import web3 from 'web3'
import Token from '../../../organisms/AssetActions/Pool/Token' import Token from '../../../organisms/AssetActions/Pool/Token'
import { useWeb3 } from '../../../../providers/Web3'
import { useUserPreferences } from '../../../../providers/UserPreferences' import { useUserPreferences } from '../../../../providers/UserPreferences'
import { fetchDataForMultipleChains } from '../../../../utils/subgraph' import {
fetchDataForMultipleChains,
calculateUserLiquidity
} from '../../../../utils/subgraph'
import NetworkName from '../../../atoms/NetworkName' import NetworkName from '../../../atoms/NetworkName'
import axios from 'axios' import axios from 'axios'
import { retrieveDDO } from '../../../../utils/aquarius' import { retrieveDDO } from '../../../../utils/aquarius'
@ -59,17 +60,6 @@ interface Asset {
createTime: number 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[]) { function findValidToken(tokens: PoolSharePoolIdTokens[]) {
const symbol = tokens.find((token) => token.tokenId !== null) const symbol = tokens.find((token) => token.tokenId !== null)
return symbol.tokenId.symbol return symbol.tokenId.symbol

View File

@ -1,6 +1,6 @@
import { Logger } from '@oceanprotocol/lib' import { Logger } from '@oceanprotocol/lib'
import { QueryResult } from '@oceanprotocol/lib/dist/node/metadatacache/MetadataCache' 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 AssetList from '../../../organisms/AssetList'
import axios from 'axios' import axios from 'axios'
import { import {

View File

@ -16,7 +16,7 @@
} }
} }
.content { .tabs {
margin-top: var(--spacer); margin-top: var(--spacer);
background-color: var(--background-body); background-color: var(--background-body);
} }

View File

@ -5,8 +5,8 @@ import PoolTransactions from '../../../molecules/PoolTransactions'
import PublishedList from './PublishedList' import PublishedList from './PublishedList'
import Downloads from './Downloads' import Downloads from './Downloads'
import ComputeJobs from './ComputeJobs' import ComputeJobs from './ComputeJobs'
import { useLocation } from '@reach/router'
import styles from './index.module.css' import styles from './index.module.css'
import { useUserPreferences } from '../../../../providers/UserPreferences'
import OceanProvider from '../../../../providers/Ocean' import OceanProvider from '../../../../providers/Ocean'
import { useWeb3 } from '../../../../providers/Web3' import { useWeb3 } from '../../../../providers/Web3'
@ -53,20 +53,17 @@ export default function HistoryPage({
}: { }: {
accountIdentifier: string accountIdentifier: string
}): ReactElement { }): ReactElement {
const { chainIds } = useUserPreferences()
const { accountId } = useWeb3() 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 defaultTab = url.searchParams.get('defaultTab')
const tabs = getTabs(accountIdentifier, accountId) const tabs = getTabs(accountIdentifier, accountId)
let defaultTabIndex = 0 let defaultTabIndex = 0
defaultTab === 'ComputeJobs' ? (defaultTabIndex = 4) : (defaultTabIndex = 0) defaultTab === 'ComputeJobs' ? (defaultTabIndex = 4) : (defaultTabIndex = 0)
return ( return (
<article className={styles.content}> <Tabs items={tabs} className={styles.tabs} defaultIndex={defaultTabIndex} />
<Tabs
items={tabs}
className={styles.tabs}
defaultIndex={defaultTabIndex}
/>
</article>
) )
} }

View File

@ -1,3 +1,7 @@
.links {
width: 100%;
}
.links, .links,
.links a { .links a {
font-size: var(--font-size-small); font-size: var(--font-size-small);
@ -7,6 +11,7 @@
.links a { .links a {
margin-left: calc(var(--spacer) / 3); margin-left: calc(var(--spacer) / 3);
color: inherit; color: inherit;
display: inline-block;
} }
.links a:first-child { .links a:first-child {
@ -19,5 +24,5 @@
} }
.linksExternal { .linksExternal {
composes: linksExternal from './index.module.css'; composes: linksExternal from '../../atoms/Publisher/index.module.css';
} }

View File

@ -1,15 +1,25 @@
import React, { ReactElement } from 'react' import React, { ReactElement } from 'react'
import styles from './PublisherLinks.module.css' import classNames from 'classnames/bind'
import { ProfileLink } from '../../../models/Profile' import { ProfileLink } from '../../../models/Profile'
import { ReactComponent as External } from '../../../images/external.svg' import { ReactComponent as External } from '../../../images/external.svg'
import styles from './PublisherLinks.module.css'
const cx = classNames.bind(styles)
export default function PublisherLinks({ export default function PublisherLinks({
links links,
className
}: { }: {
links: ProfileLink[] links: ProfileLink[]
className: string
}): ReactElement { }): ReactElement {
const styleClasses = cx({
links: true,
[className]: className
})
return ( return (
<div className={styles.links}> <div className={styleClasses}>
{' — '} {' — '}
{links?.map((link: ProfileLink) => { {links?.map((link: ProfileLink) => {
const href = const href =

View File

@ -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);
}

View File

@ -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<DDO[]>()
const [numberOfAssets, setNumberOfAssets] = useState(0)
const [sold, setSold] = useState(0)
const [tvl, setTvl] = useState<UserTVL>()
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 (
<div className={styles.stats}>
<NumberUnit label="Published" value={numberOfAssets} />
<NumberUnit label="Sold" value={sold} />
<NumberUnit
label="Total Value Locked"
value={<Conversion price={tvl?.price} hideApproximateSymbol />}
/>
</div>
)
}

View File

@ -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 (
<>
<AccountHeader accountId={accountId} />
<HistoryPage accountIdentifier={accountId} />
</>
)
}

View File

@ -5,7 +5,7 @@ import Container from '../atoms/Container'
export interface PageProps { export interface PageProps {
children: ReactNode children: ReactNode
title: string title?: string
uri: string uri: string
description?: string description?: string
noPageHeader?: boolean noPageHeader?: boolean

View File

@ -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<string>()
useEffect(() => {
setAccountId(props.location.pathname.split('/')[2])
}, [props.location.pathname])
return (
<Page title={title} uri={props.uri}>
<AccountPage accountIdentifier={accountId} />
</Page>
)
}
export const contentQuery = graphql`
query AccountPageQuery {
content: allFile(filter: { relativePath: { eq: "pages/account.json" } }) {
edges {
node {
childPagesJson {
title
description
}
}
}
}
}
`

View File

@ -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<string>()
// 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 (
<Page uri={props.uri} title={accountTruncate(finalAccountId)} noPageHeader>
<ProfilePage accountId={finalAccountId} />
</Page>
)
}
export const contentQuery = graphql`
query ProfilePageQuery {
content: allFile(filter: { relativePath: { eq: "pages/profile.json" } }) {
edges {
node {
childPagesJson {
title
description
}
}
}
}
}
`

View File

@ -20,6 +20,15 @@ import {
HighestLiquidityAssets_pools as HighestLiquidityAssetsPools, HighestLiquidityAssets_pools as HighestLiquidityAssetsPools,
HighestLiquidityAssets as HighestLiquidityGraphAssets HighestLiquidityAssets as HighestLiquidityGraphAssets
} from '../@types/apollo/HighestLiquidityAssets' } 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 { export interface PriceList {
[key: string]: string [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 { export function getSubgraphUri(chainId: number): string {
const config = getOceanConfig(chainId) const config = getOceanConfig(chainId)
return config.subgraphUri return config.subgraphUri
@ -497,3 +543,64 @@ export async function getHighestLiquidityDIDs(
.replace(/(did:op:)/g, '0x') .replace(/(did:op:)/g, '0x')
return [searchDids, didList.length] return [searchDids, didList.length]
} }
export async function getAccountNumberOfOrders(
accountId: string,
chainIds: number[]
): Promise<number> {
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<UserTVL> {
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()
}
}

View File

@ -23,8 +23,8 @@
"link": "/publish" "link": "/publish"
}, },
{ {
"name": "Account", "name": "Profile",
"link": "/account" "link": "/profile"
} }
] ]
} }