mirror of
https://github.com/oceanprotocol/market.git
synced 2024-12-02 05:57:29 +01:00
3Box publisher profiles (#264)
* install 3box Signed-off-by: mihaisc <mihai.scarlat@smartcontrol.ro> * tinkering * check tweak * load library on init only * add profile Signed-off-by: mihaisc <mihai.scarlat@smartcontrol.ro> * get 3box profile Signed-off-by: mihaisc <mihai.scarlat@smartcontrol.ro> * fix return type Signed-off-by: mihaisc <mihai.scarlat@smartcontrol.ro> * remove console.log * fix travis Signed-off-by: mihaisc <mihai.scarlat@smartcontrol.ro> * fix eslit Signed-off-by: mihaisc <mihai.scarlat@smartcontrol.ro> * fix travis Signed-off-by: mihaisc <mihai.scarlat@smartcontrol.ro> * 3box data structure tweaks, prepare output in byline * refactor * new Publisher component * tweaks * remove data partners * link/profile splitup * profile tweaks * component splitup * lots of styling, add image * affordance for publisher, refactor, server response tinkering * use 3Box proxy * open all 3box links in new tab/window * mobile fixes Co-authored-by: Matthias Kretschmann <m@kretschmann.io>
This commit is contained in:
parent
35d9b6faec
commit
dac47bf524
83
package-lock.json
generated
83
package-lock.json
generated
@ -3566,40 +3566,16 @@
|
||||
"web3-eth-contract": "^1.3.0"
|
||||
}
|
||||
},
|
||||
"@oceanprotocol/list-datapartners": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@oceanprotocol/list-datapartners/-/list-datapartners-1.0.3.tgz",
|
||||
"integrity": "sha512-MMyy81FvnRGwl2cQ4+cucq/YWjUTGzStHyAUVM6P2pFA8zMc3jouuWN2WSAjmvhxeKZU7jvJRwZCoi+miEYKjw=="
|
||||
},
|
||||
"@oceanprotocol/react": {
|
||||
"version": "0.3.21",
|
||||
"resolved": "https://registry.npmjs.org/@oceanprotocol/react/-/react-0.3.21.tgz",
|
||||
"integrity": "sha512-3fY3oYbJ1I2bTZzWqaHbQvVCI9Xdn3Oa0BOeWPA0757wT6gOQzdMcE/zKAFqzI6L/4006kTQF3Ls0UnKVkocUA==",
|
||||
"version": "0.3.22",
|
||||
"resolved": "https://registry.npmjs.org/@oceanprotocol/react/-/react-0.3.22.tgz",
|
||||
"integrity": "sha512-pPXi+4syzYWczfJXd292Wu7eJoaUZ1cjB7Js3TWE5E/YAZzQAYuaiUn9Lh5P8vHmxz8dl4Xn586jhRPWzyIRIw==",
|
||||
"requires": {
|
||||
"@oceanprotocol/lib": "^0.9.18",
|
||||
"axios": "^0.21.0",
|
||||
"decimal.js": "^10.2.1",
|
||||
"web3": "^1.3.0",
|
||||
"web3modal": "^1.9.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@oceanprotocol/lib": {
|
||||
"version": "0.9.18",
|
||||
"resolved": "https://registry.npmjs.org/@oceanprotocol/lib/-/lib-0.9.18.tgz",
|
||||
"integrity": "sha512-RsP4CjAnauI2kDH0923LOO3NhdKNB1y8WwpAviVwIwz9sYqsIIcac6MKXIm5oDeLNhmCIhJXbwvQehf17wRL5Q==",
|
||||
"requires": {
|
||||
"@ethereum-navigator/navigator": "^0.5.0",
|
||||
"@oceanprotocol/contracts": "^0.5.7",
|
||||
"decimal.js": "^10.2.0",
|
||||
"fs": "0.0.1-security",
|
||||
"lzma": "^2.3.2",
|
||||
"node-fetch": "^2.6.1",
|
||||
"save-file": "^2.3.1",
|
||||
"uuid": "^8.3.0",
|
||||
"web3": "^1.3.0",
|
||||
"web3-eth-contract": "^1.3.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@oceanprotocol/typographies": {
|
||||
@ -5807,17 +5783,6 @@
|
||||
"@types/reactcss": "*"
|
||||
}
|
||||
},
|
||||
"@types/react-datepicker": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-datepicker/-/react-datepicker-3.1.1.tgz",
|
||||
"integrity": "sha512-vwNrgaIMJThvvwmtnA8jSVVJZ0FNgljQrq1jDA4MtYJIDmVmd9NNrFaXf9u2JqR2nS+8Kvi8OVs/tnAbUqZhHw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/react": "*",
|
||||
"date-fns": "^2.0.1",
|
||||
"popper.js": "^1.14.1"
|
||||
}
|
||||
},
|
||||
"@types/react-helmet": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-helmet/-/react-helmet-6.1.0.tgz",
|
||||
@ -23236,6 +23201,11 @@
|
||||
"resolved": "https://registry.npmjs.org/junk/-/junk-3.1.0.tgz",
|
||||
"integrity": "sha512-pBxcB3LFc8QVgdggvZWyeys+hnrNWg4OcZIU/1X59k5jQdLBlCsYGRQaz234SqoRLTCgMH00fY0xRJH+F9METQ=="
|
||||
},
|
||||
"jwt-decode": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz",
|
||||
"integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A=="
|
||||
},
|
||||
"keccak": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.1.tgz",
|
||||
@ -26351,7 +26321,8 @@
|
||||
"popper.js": {
|
||||
"version": "1.16.1",
|
||||
"resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
|
||||
"integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ=="
|
||||
"integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==",
|
||||
"dev": true
|
||||
},
|
||||
"portfinder": {
|
||||
"version": "1.0.28",
|
||||
@ -27586,14 +27557,6 @@
|
||||
"object-assign": "^4.1.1"
|
||||
}
|
||||
},
|
||||
"react-alice-carousel": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react-alice-carousel/-/react-alice-carousel-2.0.2.tgz",
|
||||
"integrity": "sha512-Uy9tOPAmwazPbq9uTygkk0zvCbMDWrtiBk7XnK5DTxkN3dLGxaCL3AKiCLLQOfnKbxZRGyXROLjlGYZ1aEpALg==",
|
||||
"requires": {
|
||||
"vanilla-swipe": "^2.2.0"
|
||||
}
|
||||
},
|
||||
"react-chartjs-2": {
|
||||
"version": "2.11.1",
|
||||
"resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-2.11.1.tgz",
|
||||
@ -27628,18 +27591,6 @@
|
||||
"shortid": "^2.2.15"
|
||||
}
|
||||
},
|
||||
"react-datepicker": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-3.3.0.tgz",
|
||||
"integrity": "sha512-QnIlBxDSWEGBi2X5P1BqWzvfnPFRKhtrsgAcujUVwyWeID/VatFaAOEjEjfD1bXR9FuSYVLlLR3j/vbG19hWOA==",
|
||||
"requires": {
|
||||
"classnames": "^2.2.6",
|
||||
"date-fns": "^2.0.1",
|
||||
"prop-types": "^15.7.2",
|
||||
"react-onclickoutside": "^6.9.0",
|
||||
"react-popper": "^1.3.4"
|
||||
}
|
||||
},
|
||||
"react-dev-utils": {
|
||||
"version": "10.2.1",
|
||||
"resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-10.2.1.tgz",
|
||||
@ -28288,11 +28239,6 @@
|
||||
"warning": "^4.0.3"
|
||||
}
|
||||
},
|
||||
"react-onclickoutside": {
|
||||
"version": "6.9.0",
|
||||
"resolved": "https://registry.npmjs.org/react-onclickoutside/-/react-onclickoutside-6.9.0.tgz",
|
||||
"integrity": "sha512-8ltIY3bC7oGhj2nPAvWOGi+xGFybPNhJM0V1H8hY/whNcXgmDeaeoCMPPd8VatrpTsUWjb/vGzrmu6SrXVty3A=="
|
||||
},
|
||||
"react-paginate": {
|
||||
"version": "6.5.0",
|
||||
"resolved": "https://registry.npmjs.org/react-paginate/-/react-paginate-6.5.0.tgz",
|
||||
@ -28305,6 +28251,7 @@
|
||||
"version": "1.3.7",
|
||||
"resolved": "https://registry.npmjs.org/react-popper/-/react-popper-1.3.7.tgz",
|
||||
"integrity": "sha512-nmqYTx7QVjCm3WUZLeuOomna138R1luC4EqkW3hxJUrAe+3eNz3oFCLYdnPwILfn0mX1Ew2c3wctrjlUMYYUww==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.1.2",
|
||||
"create-react-context": "^0.3.0",
|
||||
@ -32889,7 +32836,8 @@
|
||||
"typed-styles": {
|
||||
"version": "0.0.7",
|
||||
"resolved": "https://registry.npmjs.org/typed-styles/-/typed-styles-0.0.7.tgz",
|
||||
"integrity": "sha512-pzP0PWoZUhsECYjABgCGQlRGL1n7tOHsgwYv3oIiEpJwGhFTuty/YNeduxQYzXXa3Ge5BdT6sHYIQYpl4uJ+5Q=="
|
||||
"integrity": "sha512-pzP0PWoZUhsECYjABgCGQlRGL1n7tOHsgwYv3oIiEpJwGhFTuty/YNeduxQYzXXa3Ge5BdT6sHYIQYpl4uJ+5Q==",
|
||||
"dev": true
|
||||
},
|
||||
"typedarray": {
|
||||
"version": "0.0.6",
|
||||
@ -33601,11 +33549,6 @@
|
||||
"spdx-expression-parse": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"vanilla-swipe": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/vanilla-swipe/-/vanilla-swipe-2.2.0.tgz",
|
||||
"integrity": "sha512-iNXEIpPTe2KMOzHyi0lKP9rSSHry+SoHAc0aBHH4xcJBjKX5cnw6DcxgT3OoWsqxHQ6O1wmG4PAG9BOzzR5jGQ=="
|
||||
},
|
||||
"varint": {
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/varint/-/varint-5.0.2.tgz",
|
||||
|
@ -25,7 +25,6 @@
|
||||
"@loadable/component": "^5.14.1",
|
||||
"@oceanprotocol/art": "^3.0.0",
|
||||
"@oceanprotocol/lib": "^0.9.18",
|
||||
"@oceanprotocol/list-datapartners": "^1.0.3",
|
||||
"@oceanprotocol/react": "^0.3.22",
|
||||
"@oceanprotocol/typographies": "^0.1.0",
|
||||
"@sindresorhus/slugify": "^1.0.0",
|
||||
@ -62,14 +61,13 @@
|
||||
"gatsby-transformer-sharp": "^2.5.21",
|
||||
"intersection-observer": "^0.11.0",
|
||||
"is-url-superb": "^4.0.0",
|
||||
"jwt-decode": "^3.1.2",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"lodash.omit": "^4.5.0",
|
||||
"query-string": "^6.13.7",
|
||||
"react": "^17.0.1",
|
||||
"react-alice-carousel": "^2.0.2",
|
||||
"react-chartjs-2": "^2.11.1",
|
||||
"react-data-table-component": "^6.11.5",
|
||||
"react-datepicker": "^3.3.0",
|
||||
"react-dom": "^17.0.1",
|
||||
"react-dotdotdot": "^1.3.1",
|
||||
"react-dropzone": "^11.2.3",
|
||||
@ -104,7 +102,6 @@
|
||||
"@types/lodash.omit": "^4.5.6",
|
||||
"@types/node": "^14.14.6",
|
||||
"@types/react": "^16.9.56",
|
||||
"@types/react-datepicker": "^3.1.1",
|
||||
"@types/react-helmet": "^6.1.0",
|
||||
"@types/react-modal": "^3.10.6",
|
||||
"@types/react-paginate": "^6.2.1",
|
||||
|
@ -1,24 +0,0 @@
|
||||
.partner {
|
||||
font-weight: var(--font-weight-bold);
|
||||
margin-top: calc(var(--spacer) / 8);
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.badge {
|
||||
composes: badge from '../atoms/Badge.module.css';
|
||||
border-radius: 50%;
|
||||
width: var(--font-size-h4);
|
||||
height: var(--font-size-h4);
|
||||
padding: 0.15rem;
|
||||
vertical-align: middle;
|
||||
margin-right: 0.2rem;
|
||||
position: relative;
|
||||
top: -0.1rem;
|
||||
}
|
||||
|
||||
.badge svg {
|
||||
fill: currentColor;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
import { ReactComponent as PartnerIcon } from '../../images/partner.svg'
|
||||
import styles from './Partner.module.css'
|
||||
import classNames from 'classnames/bind'
|
||||
import Tooltip from './Tooltip'
|
||||
import { PartnerData } from '@oceanprotocol/list-datapartners/types'
|
||||
|
||||
const cx = classNames.bind(styles)
|
||||
|
||||
export function PartnerBadge(): ReactElement {
|
||||
return (
|
||||
<span className={styles.badge}>
|
||||
<PartnerIcon />
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
export default function Partner({
|
||||
partner,
|
||||
className
|
||||
}: {
|
||||
partner: PartnerData
|
||||
className?: string
|
||||
}): ReactElement {
|
||||
const styleClasses = cx({
|
||||
partner: true,
|
||||
[className]: className
|
||||
})
|
||||
|
||||
return (
|
||||
<span className={styleClasses}>
|
||||
<Tooltip
|
||||
content={
|
||||
<>
|
||||
Ocean Protocol{' '}
|
||||
<a href="https://github.com/oceanprotocol/list-datapartners">
|
||||
Data Partner
|
||||
</a>
|
||||
</>
|
||||
}
|
||||
placement="top"
|
||||
>
|
||||
<PartnerBadge />
|
||||
{partner.name}
|
||||
</Tooltip>
|
||||
</span>
|
||||
)
|
||||
}
|
7
src/components/atoms/Publisher/Add.module.css
Normal file
7
src/components/atoms/Publisher/Add.module.css
Normal file
@ -0,0 +1,7 @@
|
||||
.add {
|
||||
color: var(--brand-pink);
|
||||
}
|
||||
|
||||
.linksExternal {
|
||||
composes: linksExternal from './index.module.css';
|
||||
}
|
16
src/components/atoms/Publisher/Add.tsx
Normal file
16
src/components/atoms/Publisher/Add.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
import { ReactComponent as External } from '../../../images/external.svg'
|
||||
import styles from './Add.module.css'
|
||||
|
||||
export default function Add(): ReactElement {
|
||||
return (
|
||||
<a
|
||||
className={styles.add}
|
||||
href="https://www.3box.io/hub"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Add profile on 3Box <External className={styles.linksExternal} />
|
||||
</a>
|
||||
)
|
||||
}
|
71
src/components/atoms/Publisher/ProfileDetails.module.css
Normal file
71
src/components/atoms/Publisher/ProfileDetails.module.css
Normal file
@ -0,0 +1,71 @@
|
||||
.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) / 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);
|
||||
}
|
51
src/components/atoms/Publisher/ProfileDetails.tsx
Normal file
51
src/components/atoms/Publisher/ProfileDetails.tsx
Normal file
@ -0,0 +1,51 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
import styles from './ProfileDetails.module.css'
|
||||
import { Profile } from '../../../models/Profile'
|
||||
import EtherscanLink from '../EtherscanLink'
|
||||
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>
|
||||
|
||||
<EtherscanLink networkId={networkId} path={`address/${account}`}>
|
||||
<code>{account}</code>
|
||||
</EtherscanLink>
|
||||
</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>
|
||||
</>
|
||||
)
|
||||
}
|
23
src/components/atoms/Publisher/PublisherLinks.module.css
Normal file
23
src/components/atoms/Publisher/PublisherLinks.module.css
Normal file
@ -0,0 +1,23 @@
|
||||
.links,
|
||||
.links a {
|
||||
font-size: var(--font-size-small);
|
||||
color: var(--color-secondary);
|
||||
}
|
||||
|
||||
.links a {
|
||||
margin-left: calc(var(--spacer) / 3);
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.links a:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.links a:hover,
|
||||
.links a:focus {
|
||||
color: var(--brand-pink);
|
||||
}
|
||||
|
||||
.linksExternal {
|
||||
composes: linksExternal from './index.module.css';
|
||||
}
|
30
src/components/atoms/Publisher/PublisherLinks.tsx
Normal file
30
src/components/atoms/Publisher/PublisherLinks.tsx
Normal file
@ -0,0 +1,30 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
import styles from './PublisherLinks.module.css'
|
||||
import { ProfileLink } from '../../../models/Profile'
|
||||
import { ReactComponent as External } from '../../../images/external.svg'
|
||||
|
||||
export default function PublisherLinks({
|
||||
links
|
||||
}: {
|
||||
links: ProfileLink[]
|
||||
}): ReactElement {
|
||||
return (
|
||||
<div className={styles.links}>
|
||||
{' — '}
|
||||
{links?.map((link: ProfileLink) => {
|
||||
const href =
|
||||
link.name === 'Twitter'
|
||||
? `https://twitter.com/${link.value}`
|
||||
: link.name === 'GitHub'
|
||||
? `https://github.com/${link.value}`
|
||||
: link.value
|
||||
|
||||
return (
|
||||
<a href={href} key={link.name} target="_blank" rel="noreferrer">
|
||||
{link.name} <External className={styles.linksExternal} />
|
||||
</a>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
47
src/components/atoms/Publisher/index.module.css
Normal file
47
src/components/atoms/Publisher/index.module.css
Normal file
@ -0,0 +1,47 @@
|
||||
.publisher {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@media (min-width: 32rem) {
|
||||
.publisher {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
99
src/components/atoms/Publisher/index.tsx
Normal file
99
src/components/atoms/Publisher/index.tsx
Normal file
@ -0,0 +1,99 @@
|
||||
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 EtherscanLink from '../EtherscanLink'
|
||||
import { accountTruncate } from '../../../utils/wallet'
|
||||
import axios from 'axios'
|
||||
import { useOcean } from '@oceanprotocol/react'
|
||||
import { ReactComponent as Info } from '../../../images/info.svg'
|
||||
import ProfileDetails from './ProfileDetails'
|
||||
import Add from './Add'
|
||||
|
||||
const cx = classNames.bind(styles)
|
||||
|
||||
export default function Publisher({
|
||||
account,
|
||||
minimal,
|
||||
className
|
||||
}: {
|
||||
account: string
|
||||
minimal?: boolean
|
||||
className?: string
|
||||
}): ReactElement {
|
||||
const { networkId, accountId } = useOcean()
|
||||
const [profile, setProfile] = useState<Profile>()
|
||||
const [name, setName] = useState<string>()
|
||||
|
||||
const showAdd = account === accountId && !profile
|
||||
|
||||
useEffect(() => {
|
||||
if (!account) return
|
||||
|
||||
setName(accountTruncate(account))
|
||||
const source = axios.CancelToken.source()
|
||||
|
||||
async function get3Box() {
|
||||
const profile = await get3BoxProfile(account, source.token)
|
||||
if (!profile) return
|
||||
|
||||
setProfile(profile)
|
||||
const { name } = profile
|
||||
name && setName(name)
|
||||
}
|
||||
get3Box()
|
||||
|
||||
return () => {
|
||||
source.cancel()
|
||||
}
|
||||
}, [account])
|
||||
|
||||
const styleClasses = cx({
|
||||
publisher: true,
|
||||
[className]: className
|
||||
})
|
||||
|
||||
return (
|
||||
<div className={styleClasses}>
|
||||
{minimal ? (
|
||||
name
|
||||
) : (
|
||||
<>
|
||||
<Link
|
||||
to={`/search/?owner=${account}`}
|
||||
title="Show all data sets created by this account."
|
||||
>
|
||||
{name}
|
||||
</Link>
|
||||
|
||||
<div className={styles.links}>
|
||||
{' — '}
|
||||
{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 />}
|
||||
<EtherscanLink networkId={networkId} path={`address/${account}`}>
|
||||
Etherscan
|
||||
</EtherscanLink>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
@ -5,6 +5,7 @@
|
||||
.content {
|
||||
composes: box from './Box.module.css';
|
||||
padding: calc(var(--spacer) / 4);
|
||||
width: calc(100% - var(--spacer) / 3);
|
||||
max-width: 25rem;
|
||||
font-size: var(--font-size-small);
|
||||
}
|
||||
|
@ -41,7 +41,7 @@ export default function Tooltip({
|
||||
|
||||
function onMount() {
|
||||
setSpring({
|
||||
transform: 'scale(1) translateY(0)',
|
||||
...animation.to,
|
||||
onRest: (): void => null,
|
||||
config: animation.config
|
||||
})
|
||||
|
@ -2,25 +2,20 @@ import { DDO } from '@oceanprotocol/lib'
|
||||
import { useOcean } from '@oceanprotocol/react'
|
||||
import { Link } from 'gatsby'
|
||||
import React, { ReactElement, useEffect, useState } from 'react'
|
||||
import { useDataPartner } from '../../hooks/useDataPartner'
|
||||
import { retrieveDDO } from '../../utils/aquarius'
|
||||
import { PartnerBadge } from '../atoms/Partner'
|
||||
import styles from './AssetListTitle.module.css'
|
||||
import axios from 'axios'
|
||||
|
||||
export default function AssetListTitle({
|
||||
ddo,
|
||||
did,
|
||||
title,
|
||||
owner
|
||||
title
|
||||
}: {
|
||||
ddo?: DDO
|
||||
did?: string
|
||||
title?: string
|
||||
owner?: string
|
||||
}): ReactElement {
|
||||
const { config } = useOcean()
|
||||
const { partner } = useDataPartner(owner)
|
||||
const [assetTitle, setAssetTitle] = useState<string>(title)
|
||||
|
||||
useEffect(() => {
|
||||
@ -49,9 +44,7 @@ export default function AssetListTitle({
|
||||
|
||||
return (
|
||||
<h3 className={styles.title}>
|
||||
<Link to={`/asset/${did || ddo.id}`}>
|
||||
{partner && <PartnerBadge />} {assetTitle}
|
||||
</Link>
|
||||
<Link to={`/asset/${did || ddo.id}`}>{assetTitle}</Link>
|
||||
</h3>
|
||||
)
|
||||
}
|
||||
|
@ -6,9 +6,8 @@ import styles from './AssetTeaser.module.css'
|
||||
import { DDO } from '@oceanprotocol/lib'
|
||||
import removeMarkdown from 'remove-markdown'
|
||||
import Tooltip from '../atoms/Tooltip'
|
||||
import Publisher from '../atoms/Publisher'
|
||||
import { useMetadata } from '@oceanprotocol/react'
|
||||
import Partner from '../atoms/Partner'
|
||||
import { useDataPartner } from '../../hooks/useDataPartner'
|
||||
|
||||
declare type AssetTeaserProps = {
|
||||
ddo: DDO
|
||||
@ -16,8 +15,6 @@ declare type AssetTeaserProps = {
|
||||
|
||||
const AssetTeaser: React.FC<AssetTeaserProps> = ({ ddo }: AssetTeaserProps) => {
|
||||
const { owner } = useMetadata(ddo)
|
||||
const { partner } = useDataPartner(owner)
|
||||
|
||||
const { attributes } = ddo.findServiceByType('metadata')
|
||||
const { name } = attributes.main
|
||||
const { dataTokenInfo } = ddo
|
||||
@ -34,7 +31,7 @@ const AssetTeaser: React.FC<AssetTeaserProps> = ({ ddo }: AssetTeaserProps) => {
|
||||
{dataTokenInfo?.symbol}
|
||||
</Tooltip>
|
||||
<h1 className={styles.title}>{name}</h1>
|
||||
{partner && <Partner className={styles.partner} partner={partner} />}
|
||||
<Publisher account={owner} minimal />
|
||||
{isCompute && <div className={styles.accessLabel}>Compute</div>}
|
||||
|
||||
<div className={styles.content}>
|
||||
|
@ -54,13 +54,7 @@ const columns = [
|
||||
name: 'Data Set',
|
||||
selector: function getAssetRow(row: DDO) {
|
||||
const { attributes } = row.findServiceByType('metadata')
|
||||
return (
|
||||
<AssetTitle
|
||||
title={attributes.main.name}
|
||||
ddo={row}
|
||||
owner={row.publicKey[0].owner}
|
||||
/>
|
||||
)
|
||||
return <AssetTitle title={attributes.main.name} ddo={row} />
|
||||
},
|
||||
maxWidth: '45rem',
|
||||
grow: 1
|
||||
|
@ -1,31 +0,0 @@
|
||||
.byline {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
@media (min-width: 40rem) {
|
||||
.bylineLinks {
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
|
||||
.bylineLinks a {
|
||||
margin-left: calc(var(--spacer) / 3);
|
||||
color: inherit;
|
||||
font-size: var(--font-size-mini);
|
||||
}
|
||||
|
||||
.bylineLinks a:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.bylineLinks a:hover,
|
||||
.bylineLinks a:focus {
|
||||
color: var(--brand-pink);
|
||||
}
|
||||
|
||||
.bylineExternal {
|
||||
width: 0.6em;
|
||||
height: 0.6em;
|
||||
display: inline-block;
|
||||
fill: var(--brand-grey-light);
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
import styles from './Byline.module.css'
|
||||
import { accountTruncate } from '../../../utils/wallet'
|
||||
import Partner from '../../atoms/Partner'
|
||||
import { ReactComponent as External } from '../../../images/external.svg'
|
||||
import { Link } from 'gatsby'
|
||||
import EtherscanLink from '../../atoms/EtherscanLink'
|
||||
import { useOcean } from '@oceanprotocol/react'
|
||||
import { useDataPartner } from '../../../hooks/useDataPartner'
|
||||
|
||||
export default function Byline({
|
||||
owner,
|
||||
prefix
|
||||
}: {
|
||||
owner: string
|
||||
prefix?: string
|
||||
}): ReactElement {
|
||||
const { networkId } = useOcean()
|
||||
const { partner } = useDataPartner(owner)
|
||||
|
||||
return (
|
||||
<div className={styles.byline}>
|
||||
{prefix}
|
||||
<Link
|
||||
to={`/search/?owner=${owner}`}
|
||||
title="Show all data sets created by this account."
|
||||
>
|
||||
{partner ? (
|
||||
<Partner partner={partner} />
|
||||
) : (
|
||||
owner && accountTruncate(owner)
|
||||
)}
|
||||
</Link>
|
||||
<div className={styles.bylineLinks}>
|
||||
{' — '}
|
||||
{partner &&
|
||||
Object.entries(partner.links).map(([key, value]) => (
|
||||
<a href={value} key={key} target="_blank" rel="noreferrer">
|
||||
{key} <External className={styles.bylineExternal} />
|
||||
</a>
|
||||
))}
|
||||
<EtherscanLink networkId={networkId} path={`address/${owner}`}>
|
||||
Etherscan
|
||||
</EtherscanLink>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
@ -4,7 +4,7 @@ import MetaItem from './MetaItem'
|
||||
import styles from './MetaFull.module.css'
|
||||
import { MetadataMarket } from '../../../@types/MetaData'
|
||||
import { DDO } from '@oceanprotocol/lib'
|
||||
import Byline from './Byline'
|
||||
import Publisher from '../../atoms/Publisher'
|
||||
|
||||
export default function MetaFull({
|
||||
ddo,
|
||||
@ -18,8 +18,11 @@ export default function MetaFull({
|
||||
|
||||
return (
|
||||
<div className={styles.metaFull}>
|
||||
<MetaItem title="Author" content={metadata?.main.author} />
|
||||
<MetaItem title="Owner" content={<Byline owner={publicKey[0].owner} />} />
|
||||
<MetaItem title="Data Author" content={metadata?.main.author} />
|
||||
<MetaItem
|
||||
title="Owner"
|
||||
content={<Publisher account={publicKey[0].owner} />}
|
||||
/>
|
||||
|
||||
{metadata?.additionalInformation?.categories && (
|
||||
<MetaItem
|
||||
|
@ -34,10 +34,6 @@
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.author {
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
|
||||
.datatoken a {
|
||||
color: var(--color-secondary);
|
||||
}
|
||||
|
@ -9,10 +9,10 @@ import AssetActions from '../AssetActions'
|
||||
import { DDO } from '@oceanprotocol/lib'
|
||||
import { useUserPreferences } from '../../../providers/UserPreferences'
|
||||
import Pricing from './Pricing'
|
||||
import { useMetadata, useOcean, usePricing } from '@oceanprotocol/react'
|
||||
import { useOcean, usePricing } from '@oceanprotocol/react'
|
||||
import EtherscanLink from '../../atoms/EtherscanLink'
|
||||
import Bookmark from './Bookmark'
|
||||
import Byline from './Byline'
|
||||
import Publisher from '../../atoms/Publisher'
|
||||
import { useAsset } from '../../../providers/Asset'
|
||||
|
||||
export interface AssetContentProps {
|
||||
@ -27,7 +27,7 @@ export default function AssetContent({
|
||||
}: AssetContentProps): ReactElement {
|
||||
const { debug } = useUserPreferences()
|
||||
const { accountId, networkId } = useOcean()
|
||||
const { owner } = useMetadata(ddo)
|
||||
const { owner } = useAsset()
|
||||
const { dtSymbol, dtName } = usePricing(ddo)
|
||||
const [showPricing, setShowPricing] = useState(false)
|
||||
const { price } = useAsset()
|
||||
@ -41,9 +41,6 @@ export default function AssetContent({
|
||||
<div>
|
||||
{showPricing && <Pricing ddo={ddo} />}
|
||||
<div className={styles.content}>
|
||||
<p className={styles.author} title="Author">
|
||||
{metadata?.main.author}
|
||||
</p>
|
||||
{metadata?.additionalInformation?.categories?.length && (
|
||||
<p>
|
||||
<Link
|
||||
@ -67,7 +64,7 @@ export default function AssetContent({
|
||||
)}
|
||||
</EtherscanLink>
|
||||
</p>
|
||||
<Byline owner={owner} prefix="Published by " />
|
||||
Published By <Publisher account={owner} />
|
||||
</aside>
|
||||
|
||||
<Markdown
|
||||
|
@ -1,36 +0,0 @@
|
||||
.assetCarousel [class*='alice-carousel__dots-item'] {
|
||||
width: var(--font-size-mini);
|
||||
height: var(--font-size-mini);
|
||||
background: var(--color-secondary);
|
||||
border: 1px solid var(--font-color-heading);
|
||||
background: none;
|
||||
}
|
||||
|
||||
.assetCarousel [class*='alice-carousel__dots-item']:hover,
|
||||
.assetCarousel [class*='alice-carousel__dots-item __active'] {
|
||||
background: var(--font-color-heading) !important;
|
||||
}
|
||||
|
||||
.assetCarousel [class='alice-carousel__wrapper'] {
|
||||
padding-bottom: calc(var(--spacer) / 2);
|
||||
}
|
||||
|
||||
.assetCarousel [class='alice-carousel__stage'] > li article {
|
||||
margin-left: calc(var(--spacer) / 2);
|
||||
margin-right: calc(var(--spacer) / 2);
|
||||
}
|
||||
|
||||
.assetCarousel [class='alice-carousel__dots'] {
|
||||
margin-top: calc(var(--spacer) / 2);
|
||||
}
|
||||
|
||||
.assetCarousel [class*='alice-carousel__stage-item'] *,
|
||||
.assetCarousel [class*='alice-carousel__stage-item'] {
|
||||
line-height: var(--line-height);
|
||||
}
|
||||
|
||||
.empty {
|
||||
color: var(--color-secondary);
|
||||
font-size: var(--font-size-small);
|
||||
font-style: italic;
|
||||
}
|
@ -1,59 +0,0 @@
|
||||
import AssetTeaser from '../molecules/AssetTeaser'
|
||||
import React from 'react'
|
||||
import { QueryResult } from '@oceanprotocol/lib/dist/node/metadatacache/MetadataCache'
|
||||
import styles from './AssetQueryCarousel.module.css'
|
||||
import { DDO } from '@oceanprotocol/lib'
|
||||
import classNames from 'classnames/bind'
|
||||
import AliceCarousel from 'react-alice-carousel'
|
||||
import 'react-alice-carousel/lib/alice-carousel.css'
|
||||
|
||||
const cx = classNames.bind(styles)
|
||||
|
||||
declare type AssetQueryCarouselProps = {
|
||||
queryResult: QueryResult
|
||||
className?: string
|
||||
}
|
||||
|
||||
const responsive = {
|
||||
0: { items: 1 },
|
||||
600: { items: 2 },
|
||||
1280: { items: 3 },
|
||||
1600: { items: 4 },
|
||||
2400: { items: 6 }
|
||||
}
|
||||
|
||||
const AssetQueryCarousel: React.FC<AssetQueryCarouselProps> = ({
|
||||
queryResult,
|
||||
className
|
||||
}) => {
|
||||
const styleClasses = cx({
|
||||
assetCarousel: true,
|
||||
[className]: className
|
||||
})
|
||||
|
||||
const items =
|
||||
queryResult?.results.length > 0
|
||||
? queryResult.results.map((ddo: DDO) => (
|
||||
<AssetTeaser key={ddo.id} ddo={ddo} />
|
||||
))
|
||||
: [
|
||||
<div className={styles.empty} key="empty">
|
||||
No results found.
|
||||
</div>
|
||||
]
|
||||
|
||||
return (
|
||||
<div className={styleClasses}>
|
||||
<AliceCarousel
|
||||
items={items}
|
||||
responsive={responsive}
|
||||
paddingLeft={10}
|
||||
paddingRight={10}
|
||||
disableButtonsControls
|
||||
animationDuration={300}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default AssetQueryCarousel
|
@ -28,8 +28,7 @@ const columns = [
|
||||
{
|
||||
name: 'Data Set',
|
||||
selector: function getAssetRow(row: Asset) {
|
||||
const { owner } = row.ddo.publicKey[0]
|
||||
return <AssetTitle ddo={row.ddo} owner={owner} />
|
||||
return <AssetTitle ddo={row.ddo} />
|
||||
},
|
||||
grow: 2
|
||||
},
|
||||
|
@ -17,19 +17,6 @@
|
||||
margin-top: var(--spacer);
|
||||
}
|
||||
|
||||
.listPartners {
|
||||
composes: section;
|
||||
}
|
||||
|
||||
.listPartners > div {
|
||||
background: var(--background-highlight);
|
||||
padding-top: var(--spacer);
|
||||
padding-bottom: var(--spacer);
|
||||
min-height: 360px;
|
||||
margin-left: calc(-50vw + 50%);
|
||||
margin-right: calc(-50vw + 50%);
|
||||
}
|
||||
|
||||
.loaderWrap {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
@ -8,33 +8,9 @@ import Loader from '../atoms/Loader'
|
||||
import { useOcean } from '@oceanprotocol/react'
|
||||
import Button from '../atoms/Button'
|
||||
import Bookmarks from '../molecules/Bookmarks'
|
||||
import listPartners from '@oceanprotocol/list-datapartners'
|
||||
import Tooltip from '../atoms/Tooltip'
|
||||
import AssetQueryCarousel from '../organisms/AssetQueryCarousel'
|
||||
import axios from 'axios'
|
||||
import { queryMetadata } from '../../utils/aquarius'
|
||||
|
||||
const partnerAccounts = listPartners
|
||||
.map((partner) => partner.accounts.join(','))
|
||||
.filter((account) => account !== '')
|
||||
|
||||
const searchAccounts = JSON.stringify(partnerAccounts)
|
||||
.replace(/"/g, '')
|
||||
.replace(/,/g, ' OR ')
|
||||
.replace(/(\[|\])/g, '')
|
||||
|
||||
const queryPartners = {
|
||||
page: 1,
|
||||
offset: 100,
|
||||
query: {
|
||||
nativeSearch: 1,
|
||||
query_string: {
|
||||
query: `(publicKey.owner:${searchAccounts}) -isInPurgatory:true`
|
||||
}
|
||||
},
|
||||
sort: { created: -1 }
|
||||
}
|
||||
|
||||
const queryHighest = {
|
||||
page: 1,
|
||||
offset: 9,
|
||||
@ -116,68 +92,12 @@ function SectionQueryResult({
|
||||
}
|
||||
|
||||
export default function HomePage(): ReactElement {
|
||||
const { config } = useOcean()
|
||||
|
||||
const [queryResultPartners, setQueryResultPartners] = useState<QueryResult>()
|
||||
const [loading, setLoading] = useState(true)
|
||||
|
||||
useEffect(() => {
|
||||
if (!config?.metadataCacheUri) return
|
||||
|
||||
const source = axios.CancelToken.source()
|
||||
|
||||
async function init() {
|
||||
// TODO: remove any once ocean.js has nativeSearch typings
|
||||
const queryResultPartners = await queryMetadata(
|
||||
queryPartners as any,
|
||||
config.metadataCacheUri,
|
||||
source.token
|
||||
)
|
||||
setQueryResultPartners(queryResultPartners)
|
||||
setLoading(false)
|
||||
}
|
||||
init()
|
||||
|
||||
return () => {
|
||||
source.cancel()
|
||||
}
|
||||
}, [config?.metadataCacheUri])
|
||||
|
||||
return (
|
||||
<>
|
||||
<Container narrow className={styles.searchWrap}>
|
||||
<SearchBar size="large" />
|
||||
</Container>
|
||||
|
||||
<section className={styles.listPartners}>
|
||||
<h3>
|
||||
Data Partners{' '}
|
||||
<Tooltip
|
||||
content={
|
||||
<>
|
||||
Ocean Protocol{' '}
|
||||
<a href="https://github.com/oceanprotocol/list-datapartners">
|
||||
Data Partners
|
||||
</a>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</h3>
|
||||
{loading ? (
|
||||
<LoaderArea />
|
||||
) : (
|
||||
queryResultPartners && (
|
||||
<AssetQueryCarousel queryResult={queryResultPartners} />
|
||||
)
|
||||
)}
|
||||
{/* <Button
|
||||
style="text"
|
||||
to={`/search/?owner=${partnerAccounts?.toString()}`}
|
||||
>
|
||||
All data partner sets →
|
||||
</Button> */}
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3>Bookmarks</h3>
|
||||
<Bookmarks />
|
||||
|
@ -76,7 +76,7 @@
|
||||
margin: calc(var(--spacer) * 2) auto;
|
||||
max-width: 20%;
|
||||
border: 0;
|
||||
border-top: 2px solid var(--brand-black);
|
||||
border-top: 2px solid var(--border-color);
|
||||
}
|
||||
|
||||
.content figure {
|
||||
|
@ -1,28 +0,0 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import listPartners from '@oceanprotocol/list-datapartners'
|
||||
import { PartnerData } from '@oceanprotocol/list-datapartners/types'
|
||||
|
||||
export function useDataPartner(
|
||||
owner?: string
|
||||
): {
|
||||
partner: PartnerData
|
||||
partnerAccounts: string[]
|
||||
} {
|
||||
const [partnerAccounts, setPartnerAccounts] = useState<string[]>()
|
||||
const [partner, setPartner] = useState<PartnerData>()
|
||||
|
||||
useEffect(() => {
|
||||
const accounts = [] as string[]
|
||||
listPartners.map((partner) => accounts.push(...partner.accounts))
|
||||
setPartnerAccounts(accounts)
|
||||
|
||||
if (!owner) return
|
||||
|
||||
const partner = listPartners.filter((partner) =>
|
||||
partner.accounts.includes(owner)
|
||||
)[0]
|
||||
setPartner(partner)
|
||||
}, [owner])
|
||||
|
||||
return { partner, partnerAccounts }
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
<svg width="21" height="24" viewBox="0 0 21 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M19 13C17.897 13 17 13.896 17 15V17.5C17 18.327 16.327 19 15.5 19C14.673 19 14 18.327 14 17.5V9.119L10.5 2.119L7 9.119V17.5C7 18.327 6.327 19 5.5 19C4.673 19 4 18.327 4 17.5V15C4 13.896 3.102 13 2 13C0.898 13 0 13.896 0 15V17C0 17.276 0.224 17.5 0.5 17.5C0.776 17.5 1 17.276 1 17V15C1 14.449 1.448 14 2 14C2.552 14 3 14.449 3 15V17.5C3 18.879 4.121 20 5.5 20C6.879 20 8 18.879 8 17.5V16H9V20C9 21.654 7.654 23 6 23C4.262 23 3 21.844 3 20.25C3 19.974 2.776 19.75 2.5 19.75C2.224 19.75 2 19.974 2 20.25C2 22.388 3.72 24 6 24C8.206 24 10 22.206 10 20V16H11V20C11 22.169 12.889 24 15.125 24C17.406 24 19.063 22.423 19.063 20.25C19.063 19.974 18.839 19.75 18.563 19.75C18.287 19.75 18.063 19.974 18.063 20.25C18.063 21.844 16.828 23 15.125 23C13.431 23 12 21.626 12 20V16H13V17.5C13 18.879 14.121 20 15.5 20C16.879 20 18 18.879 18 17.5V15C18 14.449 18.448 14 19 14C19.552 14 20 14.449 20 15V16C20 16.276 20.224 16.5 20.5 16.5C20.776 16.5 21 16.276 21 16V15C21 13.896 20.103 13 19 13Z" />
|
||||
<path d="M16.967 6.734L10.938 0.729004C10.933 0.737004 10.927 0.744004 10.923 0.751004C10.929 0.761004 10.941 0.765004 10.947 0.775004L15.059 9H16.032C17.041 9 17.379 8.547 17.49 8.276C17.602 8.007 17.682 7.447 16.967 6.734Z" />
|
||||
<path d="M10.063 0.730004L4.061 6.732C3.348 7.446 3.43 8.006 3.541 8.276C3.653 8.547 3.991 9 5 9H5.941L10.052 0.776004C10.058 0.765004 10.071 0.761004 10.076 0.752004C10.073 0.744004 10.067 0.737004 10.063 0.730004Z" />
|
||||
</svg>
|
Before Width: | Height: | Size: 1.5 KiB |
35
src/models/Profile.ts
Normal file
35
src/models/Profile.ts
Normal file
@ -0,0 +1,35 @@
|
||||
export interface ProfileLink {
|
||||
name: string
|
||||
value: string
|
||||
}
|
||||
|
||||
export interface Profile {
|
||||
did: string
|
||||
name?: string
|
||||
description?: string
|
||||
emoji?: string
|
||||
image?: string
|
||||
links?: ProfileLink[]
|
||||
}
|
||||
|
||||
export interface ResponseData3Box {
|
||||
name: string
|
||||
description: string
|
||||
website: string
|
||||
status?: 'error'
|
||||
/* eslint-disable camelcase */
|
||||
proof_did: string
|
||||
proof_twitter: string
|
||||
proof_github: string
|
||||
/* eslint-enable camelcase */
|
||||
emoji: string
|
||||
job: string
|
||||
employer: string
|
||||
location: string
|
||||
memberSince: string
|
||||
image: {
|
||||
contentUrl: {
|
||||
[key: string]: string
|
||||
}
|
||||
}[]
|
||||
}
|
97
src/utils/profile.ts
Normal file
97
src/utils/profile.ts
Normal file
@ -0,0 +1,97 @@
|
||||
import { Profile, ProfileLink, ResponseData3Box } from '../models/Profile'
|
||||
import axios, { AxiosResponse, CancelToken } from 'axios'
|
||||
import jwtDecode from 'jwt-decode'
|
||||
import { Logger } from '@oceanprotocol/lib'
|
||||
|
||||
// https://docs.3box.io/api/rest-api
|
||||
const apiUri = 'https://market-stats.oceanprotocol.com/api'
|
||||
const ipfsUrl = 'https://ipfs.oceanprotocol.com'
|
||||
|
||||
function decodeProof(proofJWT: string) {
|
||||
if (!proofJWT) return
|
||||
const proof = jwtDecode(proofJWT) as any
|
||||
return proof
|
||||
}
|
||||
|
||||
function getLinks(
|
||||
website: string,
|
||||
twitterProof: string,
|
||||
githubProof: string
|
||||
): ProfileLink[] {
|
||||
// Conditionally add links if they exist
|
||||
const links = [
|
||||
...(website ? [{ name: 'Website', value: website }] : []),
|
||||
...(twitterProof
|
||||
? [
|
||||
{
|
||||
name: 'Twitter',
|
||||
value: decodeProof(twitterProof).claim.twitter_handle
|
||||
}
|
||||
]
|
||||
: []),
|
||||
...(githubProof
|
||||
? [{ name: 'GitHub', value: githubProof.split('/')[3] }]
|
||||
: [])
|
||||
]
|
||||
|
||||
return links
|
||||
}
|
||||
|
||||
function transformResponse({
|
||||
name,
|
||||
description,
|
||||
website,
|
||||
emoji,
|
||||
image,
|
||||
/* eslint-disable camelcase */
|
||||
proof_twitter,
|
||||
proof_github,
|
||||
proof_did
|
||||
}: ResponseData3Box) {
|
||||
/* eslint-enable camelcase */
|
||||
const links = getLinks(website, proof_twitter, proof_github)
|
||||
|
||||
const profile: Profile = {
|
||||
did: decodeProof(proof_did).iss,
|
||||
// Conditionally add profile items if they exist
|
||||
...(name && { name }),
|
||||
...(description && { description }),
|
||||
...(emoji && { emoji }),
|
||||
...(image && {
|
||||
image: `${ipfsUrl}/ipfs/${
|
||||
image.map(
|
||||
(img: { contentUrl: { [key: string]: string } }) =>
|
||||
img.contentUrl['/']
|
||||
)[0]
|
||||
}`
|
||||
}),
|
||||
...(links.length && { links })
|
||||
}
|
||||
|
||||
return profile
|
||||
}
|
||||
|
||||
export default async function get3BoxProfile(
|
||||
accountId: string,
|
||||
cancelToken: CancelToken
|
||||
): Promise<Profile> {
|
||||
try {
|
||||
const response: AxiosResponse<ResponseData3Box> = await axios(
|
||||
`${apiUri}/profile?address=${accountId}`,
|
||||
{ cancelToken }
|
||||
)
|
||||
|
||||
if (
|
||||
!response ||
|
||||
!response.data ||
|
||||
response.status !== 200 ||
|
||||
response.data.status === 'error'
|
||||
)
|
||||
return
|
||||
|
||||
Logger.log(`3Box profile found for ${accountId}`, response.data)
|
||||
const profile = transformResponse(response.data)
|
||||
return profile
|
||||
// eslint-disable-next-line no-empty
|
||||
} catch (error) {}
|
||||
}
|
@ -56,6 +56,7 @@ export function isDefaultNetwork(networkId: number): boolean {
|
||||
}
|
||||
|
||||
export function accountTruncate(account: string): string {
|
||||
if (!account) return
|
||||
const middle = account.substring(6, 38)
|
||||
const truncated = account.replace(middle, '…')
|
||||
return truncated
|
||||
|
Loading…
Reference in New Issue
Block a user