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

Merge pull request #30 from oceanprotocol/feature/v3

Remove the old, add the new Ocean
This commit is contained in:
Matthias Kretschmann 2020-07-15 22:30:55 +02:00 committed by GitHub
commit 74e2f95444
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
81 changed files with 1364 additions and 1290 deletions

View File

@ -1,26 +1,15 @@
#Spree config
NODE_URI='http://localhost:8545'
AQUARIUS_URI='http://aquarius:5000'
BRIZO_URI='http://localhost:8030'
BRIZO_ADDRESS='0x068ed00cf0441e4829d9784fcbe7b9e26d4bd8d0'
SECRET_STORE_URI='http://localhost:12001'
FAUCET_URI='https://localhost:3001'
RATING_URI='http://localhost:8000'
GATSBY_INFURA_PROJECT_ID="xxx"
#Nile market
#NODE_URI='https://nile.dev-ocean.com'
#AQUARIUS_URI='https://aquarius.nile.market.dev-ocean.com'
#BRIZO_URI='https://brizo.nile.market.dev-ocean.com'
#BRIZO_ADDRESS='0xeD792C5FcC8bF3322a6ba89A6e51eF0B6fB3C530'
#SECRET_STORE_URI='https://secret-store.nile.dev-ocean.com'
#FAUCET_URI='https://faucet.nile.dev-ocean.com'
#RATING_URI='https://rating.nile.market.dev-ocean.com'
# Local config
GATSBY_NODE_URI='http://localhost:8545'
GATSBY_METADATA_STORE_URI='http://aquarius:5000'
GATSBY_PROVIDER_URI='http://localhost:8030'
GATSBY_FACTORY_ADDRESS='0xxxx'
#GATSBY_OCEAN_TOKEN_ADDRESS='0xxxx'
#Pacific market
#NODE_URI='https://pacific.oceanprotocol.com'
#AQUARIUS_URI='https://aquarius.pacific.market.dev-ocean.com'
#BRIZO_URI='https://brizo.pacific.market.dev-ocean.com'
#BRIZO_ADDRESS='0xeD792C5FcC8bF3322a6ba89A6e51eF0B6fB3C530'
#SECRET_STORE_URI='https://secret-store.oceanprotocol.com'
#FAUCET_URI='https://faucet.oceanprotocol.com'
#RATING_URI='https://rating.pacific.market.dev-ocean.com'
# Rinkeby
#GATSBY_NODE_URI='https://rinkeby.infura.io/v3/GATSBY_INFURA_PROJECT_ID'
#GATSBY_METADATA_STORE_URI='https://aquarius.rinkeby.v3.dev-ocean.com'
#GATSBY_PROVIDER_URI='https://provider.rinkeby.v3.dev-ocean.com'
#GATSBY_FACTORY_ADDRESS='0xB9d406D24B310A7D821D0b782a36909e8c925471'
#GATSBY_OCEAN_TOKEN_ADDRESS='0xf6AE724aD6e6Fa89B6aBc9710C5eb692b7F57139'

View File

@ -174,13 +174,12 @@ vercel alias
## 🏗 Ocean Protocol Infrastructure
The following Aquarius & Brizo instances specifically for marketplace are deployed in Ocean Protocol's AWS K8:
The following Metadata Store & Provider instances specifically for marketplace are deployed in Ocean Protocol's AWS K8:
**Nile (Staging)**
**Rinkeby (Staging)**
- K8 namespace: `market-nile`
- `aquarius.nile.market.dev-ocean.com`
- `brizo.nile.market.dev-ocean.com`
- `[aquarius.rinkeby.v3.dev-ocean.com](https://aquarius.rinkeby.v3.dev-ocean.com)`
- `[provider.rinkeby.v3.dev-ocean.com](https://provider.rinkeby.v3.dev-ocean.com)`
Edit command with `kubectl`, e.g.:
@ -188,7 +187,7 @@ Edit command with `kubectl`, e.g.:
kubectl edit deployment -n market-nile aquarius
```
**Pacific (Production)**
**Main (Production)**
- K8 namespace: `market-pacific`
- `aquarius.pacific.market.dev-ocean.com`

View File

@ -1,19 +1,24 @@
module.exports = {
oceanConfig: {
nodeUri: process.env.NODE_URI || 'https://pacific.oceanprotocol.com',
aquariusUri:
process.env.AQUARIUS_URI ||
'https://aquarius.marketplace.oceanprotocol.com',
brizoUri:
process.env.BRIZO_URI || 'https://brizo.marketplace.oceanprotocol.com',
brizoAddress:
process.env.BRIZO_ADDRESS || '0x00c6A0BC5cD0078d6Cd0b659E8061B404cfa5704',
secretStoreUri:
process.env.SECRET_STORE_URI || 'https://secret-store.oceanprotocol.com',
faucetUri: process.env.FAUCET_URI || 'https://faucet.oceanprotocol.com',
ratingUri:
process.env.RATING_URI ||
'https://rating.pacific.marketplace.dev-ocean.com',
nodeUri:
process.env.GATSBY_NODE_URI ||
`https://rinkeby.infura.io/${process.env.GATSBY_INFURA_PROJECT_ID}`,
metadataStoreUri:
process.env.GATSBY_METADATA_STORE_URI ||
'https://aquarius.rinkeby.v3.dev-ocean.com',
providerUri:
process.env.GATSBY_PROVIDER_URI ||
'https://provider.rinkeby.v3.dev-ocean.com',
factoryAddress:
process.env.GATSBY_FACTORY_ADDRESS ||
'0xB9d406D24B310A7D821D0b782a36909e8c925471',
oceanTokenAddress:
process.env.GATSBY_OCEAN_TOKEN_ADDRESS ||
'0xf6AE724aD6e6Fa89B6aBc9710C5eb692b7F57139',
verbose: 3
}
},
// Main, Rinkeby, Kovan
// networks: [1, 4, 42],
networks: [4],
infuraProjectId: process.env.GATSBY_INFURA_PROJECT_ID || 'xxx'
}

View File

@ -1,7 +1,7 @@
{
"site": {
"siteTitle": "Ocean Market",
"siteTagline": "A marketplace to find and publish open data sets in the Ocean Network.",
"siteTagline": "A marketplace to find, publish and trade data sets in the Ocean Network.",
"siteUrl": "https://market.oceanprotocol.now.sh",
"siteIcon": "node_modules/@oceanprotocol/art/logo/favicon-white.png",
"siteImage": "../src/images/share.png",

View File

@ -1,11 +1,14 @@
require('dotenv').config()
const siteContent = require('./content/site.json')
const { oceanConfig } = require('./app.config')
const appConfig = require('./app.config')
module.exports = {
siteMetadata: {
...siteContent.site
...siteContent.site,
appConfig: {
...appConfig
}
},
plugins: [
{
@ -29,12 +32,6 @@ module.exports = {
path: `${__dirname}/node_modules/@oceanprotocol/art/`
}
},
{
resolve: 'gatsby-source-ocean',
options: {
aquariusUri: oceanConfig.aquariusUri
}
},
{
resolve: 'gatsby-plugin-sharp',
options: {

View File

@ -1,5 +1,3 @@
const path = require('path')
exports.onCreateWebpackConfig = ({ actions }) => {
actions.setWebpackConfig({
node: {
@ -9,60 +7,6 @@ exports.onCreateWebpackConfig = ({ actions }) => {
})
}
exports.createPages = async ({ graphql, actions }) => {
const { createPage } = actions
// Create pages for all assets
const assetDetailsTemplate = path.resolve(
'src/components/templates/AssetDetails.tsx'
)
const result = await graphql(`
query {
allOceanAsset {
edges {
node {
did
main {
type
name
dateCreated
author
license
price
datePublished
files {
contentType
index
}
}
additionalInformation {
description
deliveryType
termsAndConditions
access
}
}
}
}
}
`)
if (result.errors) {
throw result.errors
}
await result.data.allOceanAsset.edges.forEach(({ node }) => {
const path = `/asset/${node.did}`
createPage({
path,
component: assetDetailsTemplate,
context: { did: node.did }
})
})
}
exports.onCreatePage = async ({ page, actions }) => {
const { createPage } = actions
// page.matchPath is a special key that's used for matching pages

1106
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -22,20 +22,21 @@
"@loadable/component": "^5.13.1",
"@now/node": "^1.7.2",
"@oceanprotocol/art": "^3.0.0",
"@oceanprotocol/react": "0.0.11",
"@oceanprotocol/squid": "^2.2.0",
"@oceanprotocol/lib": "^0.1.4",
"@oceanprotocol/react": "^0.0.12",
"@oceanprotocol/typographies": "^0.1.0",
"@sindresorhus/slugify": "^1.0.0",
"@tippyjs/react": "^4.1.0",
"@types/classnames": "^2.2.10",
"@walletconnect/web3-provider": "^1.0.15",
"axios": "^0.19.2",
"classnames": "^2.2.6",
"date-fns": "^2.14.0",
"dotenv": "^8.2.0",
"ethereum-blockies": "github:MyEtherWallet/blockies",
"filesize": "^6.1.0",
"formik": "^2.1.4",
"gatsby": "^2.24.2",
"formik": "^2.1.5",
"gatsby": "^2.24.3",
"gatsby-image": "^2.4.13",
"gatsby-plugin-manifest": "^2.4.18",
"gatsby-plugin-react-helmet": "^3.3.10",
@ -53,15 +54,14 @@
"numeral": "^2.0.6",
"query-string": "^6.13.1",
"react": "^16.13.1",
"react-data-table-component": "^6.9.6",
"react-data-table-component": "^6.9.7",
"react-datepicker": "^3.1.3",
"react-dom": "^16.13.1",
"react-dotdotdot": "^1.3.1",
"react-dropzone": "^11.0.1",
"react-dropzone": "^11.0.2",
"react-helmet": "^6.1.0",
"react-markdown": "^4.3.1",
"react-paginate": "^6.3.2",
"react-rating": "^2.0.5",
"react-responsive-modal": "^5.0.2",
"react-spring": "^8.0.27",
"react-tabs": "^3.1.1",
@ -73,17 +73,17 @@
"yup": "^0.29.1"
},
"devDependencies": {
"@babel/core": "^7.10.3",
"@babel/core": "^7.10.5",
"@babel/preset-typescript": "^7.10.1",
"@storybook/addon-actions": "^6.0.0-rc.3",
"@storybook/addon-storyshots": "^6.0.0-rc.3",
"@storybook/react": "^6.0.0-rc.3",
"@storybook/addon-actions": "^6.0.0-rc.5",
"@storybook/addon-storyshots": "^6.0.0-rc.5",
"@storybook/react": "^6.0.0-rc.5",
"@svgr/webpack": "^5.4.0",
"@testing-library/jest-dom": "^5.11.0",
"@testing-library/react": "^10.4.5",
"@testing-library/jest-dom": "^5.11.1",
"@testing-library/react": "^10.4.7",
"@types/jest": "^26.0.4",
"@types/loadable__component": "^5.13.0",
"@types/node": "^14.0.22",
"@types/node": "^14.0.23",
"@types/numeral": "^0.0.28",
"@types/react": "^16.9.43",
"@types/react-datepicker": "^3.0.2",
@ -92,8 +92,8 @@
"@types/react-tabs": "^2.3.2",
"@types/shortid": "0.0.29",
"@types/yup": "^0.29.3",
"@typescript-eslint/eslint-plugin": "^3.6.0",
"@typescript-eslint/parser": "^3.6.0",
"@typescript-eslint/eslint-plugin": "^3.6.1",
"@typescript-eslint/parser": "^3.6.1",
"babel-loader": "^8.1.0",
"babel-preset-react-app": "^9.1.2",
"electron": "^9.1.0",

View File

@ -1,42 +0,0 @@
const axios = require('axios')
exports.sourceNodes = async (
{ actions, createNodeId, createContentDigest },
{ aquariusUri }
) => {
const { createNode } = actions
// Query for all assets to use in creating pages.
const result = await axios(`${aquariusUri}/api/v1/aquarius/assets`)
for (let i = 0; i < result.data.ids.length; i++) {
const did = result.data.ids[i]
const metadataResult = await axios(
`${aquariusUri}/api/v1/aquarius/assets/metadata/${did}`
)
const metadata = {
did,
...metadataResult.data.attributes
}
const nodeMeta = {
id: createNodeId(did),
parent: null,
children: [],
internal: {
type: 'OceanAsset',
contentDigest: createContentDigest(metadata),
description: `All data sets queried from ${aquariusUri}`
}
}
const node = {
...metadata,
...nodeMeta
}
await createNode(node)
}
}

View File

@ -1,3 +0,0 @@
{
"name": "gatsby-source-ocean"
}

View File

@ -1,5 +1,5 @@
import { File, MetaData, AdditionalInformation } from '@oceanprotocol/squid'
import { ServiceMetadata } from '@oceanprotocol/squid/dist/node/ddo/Service'
import { File, MetaData, AdditionalInformation } from '@oceanprotocol/lib'
import { ServiceMetadata } from '@oceanprotocol/lib/dist/node/ddo/Service'
export declare type AccessType = 'Download' | 'Compute'
@ -34,8 +34,3 @@ export interface MetaDataPublishForm {
export interface ServiceMetaDataMarket extends ServiceMetadata {
attributes: MetaDataMarket
}
// type for assets pulled into GraphQL
export interface OceanAsset extends MetaDataMarket {
did: DID
}

View File

@ -1,5 +1,7 @@
.app {
height: 100%;
background: url('../../node_modules/@oceanprotocol/art/waves/waves.svg')
no-repeat center 10rem;
/* sticky footer technique */
display: flex;

View File

@ -12,6 +12,7 @@ export interface LayoutProps {
uri: string
description?: string
noPageHeader?: boolean
headerCenter?: boolean
}
export default function Layout({
@ -19,7 +20,8 @@ export default function Layout({
title,
uri,
description,
noPageHeader
noPageHeader,
headerCenter
}: LayoutProps): ReactElement {
return (
<div className={styles.app}>
@ -29,7 +31,11 @@ export default function Layout({
<main className={styles.main}>
<Container>
{title && !noPageHeader && (
<PageHeader title={title} description={description} />
<PageHeader
title={title}
description={description}
center={headerCenter}
/>
)}
{children}
</Container>

View File

@ -1,6 +1,9 @@
import React, { ReactElement, ReactNode } from 'react'
import classNames from 'classnames/bind'
import styles from './Container.module.css'
const cx = classNames.bind(styles)
export default function Container({
children,
narrow,
@ -10,13 +13,11 @@ export default function Container({
narrow?: boolean
className?: string
}): ReactElement {
return (
<div
className={`${styles.container} ${narrow && styles.narrow} ${
className && className
}`}
>
{children}
</div>
)
const styleClasses = cx({
container: true,
narrow: narrow,
[className]: className
})
return <div className={styleClasses}>{children}</div>
}

View File

@ -1,5 +1,5 @@
import React, { ReactElement } from 'react'
import { File as FileMetaData } from '@oceanprotocol/squid'
import { File as FileMetaData } from '@oceanprotocol/lib'
import filesize from 'filesize'
import cleanupContentType from '../../utils/cleanupContentType'
import styles from './File.module.css'

View File

@ -1,57 +0,0 @@
.ratings {
display: flex;
margin-left: calc(var(--spacer) / -8);
font-weight: var(--font-weight-base);
}
/* Handle half stars our own way */
.ratings [style*='width:'] {
width: 100% !important;
clip-path: polygon(0 0, 60% 0, 60% 100%, 0% 100%);
}
.ratings [style*='width:100%'],
.ratings [style*='width: 100%'] {
width: 100% !important;
clip-path: none !important;
}
.ratings [style*='width:0%'],
.ratings [style*='width: 0%'] {
width: 0 !important;
clip-path: none !important;
}
.star {
margin-left: calc(var(--spacer) / 8);
}
.star svg {
fill: none;
stroke: var(--brand-grey);
}
.full {
composes: star;
}
.full svg {
fill: var(--color-primary);
stroke: var(--color-primary);
}
.ratingVotes {
display: inline-block;
font-size: var(--font-size-small);
padding-left: 5px;
color: var(--brand-grey);
}
.readonly {
composes: ratings;
}
.readonly .full svg {
fill: var(--color-secondary);
stroke: var(--color-secondary);
}

View File

@ -1,36 +0,0 @@
import React from 'react'
import Rating from './Rating'
export default {
title: 'Atoms/Rating'
}
export const Normal = () => (
<Rating
readonly
curation={{
rating: 3,
numVotes: 300
}}
/>
)
export const WithFraction = () => (
<Rating
readonly
curation={{
rating: 3.3,
numVotes: 300
}}
/>
)
export const Interactive = () => (
<Rating
onClick={(value: any) => null}
curation={{
rating: 3.3,
numVotes: 300
}}
/>
)

View File

@ -1,51 +0,0 @@
import React from 'react'
import ReactRating from 'react-rating'
import Star from '../../images/star.svg'
import { Curation } from '@oceanprotocol/squid'
import styles from './Rating.module.css'
export default function Rating({
curation,
readonly,
isLoading,
onClick
}: {
curation: Curation | undefined
readonly?: boolean
isLoading?: boolean
onClick?: (value: any) => void
}) {
let numVotes = 0
let rating = 0
if (!curation) return null
;({ numVotes, rating } = curation)
// if it's readonly then the fraction is 10 to show the average rating proper. When you select the rating you select from 1 to 5
const fractions = readonly ? 2 : 1
return (
<div className={`${readonly ? styles.readonly : styles.ratings}`}>
<ReactRating
emptySymbol={
<div className={styles.star}>
<Star />
</div>
}
fullSymbol={
<div className={styles.full}>
<Star />
</div>
}
initialRating={rating}
readonly={readonly || isLoading || false}
onClick={onClick}
fractions={fractions}
/>
<span className={styles.ratingVotes}>
{rating} {readonly ? `(${numVotes})` : ''}
</span>
</div>
)
}

View File

@ -7,14 +7,13 @@
.tag {
color: var(--color-secondary);
font-size: var(--font-size-small);
font-weight: var(--font-weight-bold);
padding: 0.2rem 1.2rem 0.2rem 1.2rem;
margin-left: calc(var(--spacer) / 16);
margin-right: calc(var(--spacer) / 16);
margin-bottom: calc(var(--spacer) / 8);
padding: 0.1rem 1.2rem 0.1rem 1.2rem;
margin-left: calc(var(--spacer) / 8);
margin-right: calc(var(--spacer) / 8);
margin-bottom: calc(var(--spacer) / 4);
text-align: center;
border-radius: var(--border-radius);
border: 1px solid var(--brand-grey-light);
border: 1px solid var(--brand-grey-lighter);
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
@ -25,10 +24,6 @@
color: var(--color-primary);
}
.tag:first-of-type {
margin-left: 0;
}
.more {
font-size: var(--font-size-mini);
margin-left: calc(var(--spacer) / 8);

View File

@ -30,6 +30,7 @@
.foot {
font-weight: var(--font-weight-bold);
margin-top: calc(var(--spacer) / 2);
}
.foot p {

View File

@ -1,6 +1,6 @@
import AssetTeaser from '../molecules/AssetTeaser'
import * as React from 'react'
import { DDO } from '@oceanprotocol/squid'
import { DDO } from '@oceanprotocol/lib'
import ddo from '../../../tests/unit/__fixtures__/ddo'
export default {

View File

@ -2,10 +2,8 @@ import React from 'react'
import { Link } from 'gatsby'
import Dotdotdot from 'react-dotdotdot'
import { MetaDataMarket } from '../../@types/MetaData'
import Tags from '../atoms/Tags'
import Price from '../atoms/Price'
import styles from './AssetTeaser.module.css'
import Rating from '../atoms/Rating'
declare type AssetTeaserProps = {
did: string
@ -16,17 +14,10 @@ const AssetTeaser: React.FC<AssetTeaserProps> = ({
did,
metadata
}: AssetTeaserProps) => {
if (!metadata.additionalInformation) return null
const { name, price } = metadata.main
const {
description,
copyrightHolder,
tags,
categories,
access
} = metadata.additionalInformation
const { curation } = metadata
const { description, access } = metadata.additionalInformation
return (
<article className={styles.teaser}>
@ -35,20 +26,15 @@ const AssetTeaser: React.FC<AssetTeaserProps> = ({
{access === 'Compute' && (
<div className={styles.accessLabel}>{access}</div>
)}
<Rating curation={curation} readonly />
<div className={styles.content}>
<Dotdotdot tagName="p" clamp={3}>
{description || ''}
</Dotdotdot>
{tags && (
<Tags className={styles.tags} items={tags} max={3} noLinks />
)}
</div>
<footer className={styles.foot}>
<Price price={price} small />
<Price price={price} />
</footer>
</Link>
</article>

View File

@ -1,6 +1,6 @@
import React, { useState, useEffect } from 'react'
import { useNavigate } from '@reach/router'
import { DDO } from '@oceanprotocol/squid'
import { DDO } from '@oceanprotocol/lib'
import { redeploy } from '../../utils'
import Button from '../atoms/Button'
import BaseDialog from '../atoms/BaseDialog'

View File

@ -1,5 +1,5 @@
import React, { ReactElement } from 'react'
import { File } from '@oceanprotocol/squid'
import { File } from '@oceanprotocol/lib'
import { prettySize } from '../../../utils'
import cleanupContentType from '../../../utils/cleanupContentType'
import styles from './Info.module.css'

View File

@ -14,3 +14,9 @@
margin-top: calc(var(--spacer) / 4);
margin-bottom: 0;
}
.center {
margin-left: auto;
margin-right: auto;
text-align: center;
}

View File

@ -6,14 +6,16 @@ const cx = classNames.bind(styles)
export default function PageHeader({
title,
description
description,
center
}: {
title: string
description?: string
center?: boolean
}): ReactElement {
const styleClasses = cx({
header: true
header: true,
center: center
})
return (

View File

@ -15,7 +15,7 @@
margin-top: -1px;
display: inline-block;
cursor: pointer;
border: 1px solid var(--brand-grey-light);
border: 1px solid var(--brand-grey-lighter);
min-width: 3.5rem;
}

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect } from 'react'
import React, { useState, useEffect, ReactElement } from 'react'
import ReactPaginate from 'react-paginate'
import styles from './Pagination.module.css'
@ -14,7 +14,9 @@ export default function Pagination({
currentPage,
hrefBuilder,
onPageChange
}: PaginationProps) {
}: PaginationProps): ReactElement {
if (!totalPages || totalPages < 2) return null
const [smallViewport, setSmallViewport] = useState(true)
function viewportChange(mq: { matches: boolean }) {
@ -31,7 +33,7 @@ export default function Pagination({
}
}, [])
return totalPages > 1 ? (
return (
<ReactPaginate
pageCount={totalPages}
// react-pagination starts counting at 0, we start at 1
@ -53,5 +55,5 @@ export default function Pagination({
disabledClassName={styles.prevNextDisabled}
breakLinkClassName={styles.break}
/>
) : null
)
}

View File

@ -1,7 +1,6 @@
import React, { useState, ChangeEvent, FormEvent, ReactElement } from 'react'
import { useNavigate } from '@reach/router'
import styles from './SearchBar.module.css'
import Loader from '../atoms/Loader'
import Button from '../atoms/Button'
import Input from '../atoms/Input'
import InputGroup from '../atoms/Input/InputGroup'
@ -19,7 +18,6 @@ export default function SearchBar({
}): ReactElement {
const navigate = useNavigate()
const [value, setValue] = useState(initialValue || '')
const [searchStarted, setSearchStarted] = useState(false)
function handleChange(e: ChangeEvent<HTMLInputElement>) {
setValue(e.target.value)
@ -27,10 +25,7 @@ export default function SearchBar({
function startSearch(e: FormEvent<HTMLButtonElement>) {
e.preventDefault()
if (value === '') return
setSearchStarted(true)
navigate(`/search?text=${value}`)
}
@ -46,7 +41,7 @@ export default function SearchBar({
required
/>
<Button onClick={(e: FormEvent<HTMLButtonElement>) => startSearch(e)}>
{searchStarted ? <Loader /> : 'Search'}
Search
</Button>
</InputGroup>

View File

@ -1,15 +1,14 @@
import React from 'react'
import styles from './Account.module.css'
import { useWeb3, useOcean } from '@oceanprotocol/react'
import { useOcean } from '@oceanprotocol/react'
import { toDataUrl } from 'ethereum-blockies'
import { ReactComponent as Caret } from '../../../images/caret.svg'
import Status from '../../atoms/Status'
function accountTruncate(account: string) {
const middle = account.substring(6, 38)
const truncated = account.replace(middle, '…')
return truncated
}
import {
accountTruncate,
connectWallet,
isCorrectNetwork
} from '../../../utils/wallet'
const Blockies = ({ account }: { account: string | undefined }) => {
if (!account) return null
@ -28,15 +27,14 @@ const Blockies = ({ account }: { account: string | undefined }) => {
// Forward ref for Tippy.js
// eslint-disable-next-line
const Account = React.forwardRef((props, ref: any) => {
const { account, web3Connect, ethProviderStatus } = useWeb3()
const { status } = useOcean()
const hasSuccess = ethProviderStatus === 1 && status === 1
const { accountId, status, connect, chainId } = useOcean()
const hasSuccess = status === 1 && isCorrectNetwork(chainId)
return account ? (
return accountId ? (
<button className={styles.button} aria-label="Account" ref={ref}>
<Blockies account={account} />
<span className={styles.address} title={account}>
{accountTruncate(account)}
<Blockies account={accountId} />
<span className={styles.address} title={accountId}>
{accountTruncate(accountId)}
</span>
{!hasSuccess && (
<Status className={styles.status} state="warning" aria-hidden />
@ -46,7 +44,7 @@ const Account = React.forwardRef((props, ref: any) => {
) : (
<button
className={styles.button}
onClick={() => web3Connect.connect()}
onClick={async () => await connectWallet(connect)}
// Need the `ref` here although we do not want
// the Tippy to show in this state.
ref={ref}

View File

@ -5,21 +5,31 @@
max-width: 25rem;
}
.details > ul {
margin-bottom: calc(var(--spacer) / 4);
}
.balance {
font-size: var(--font-size-base);
font-weight: var(--font-weight-bold);
color: var(--color-secondary);
white-space: nowrap;
font-size: var(--font-size-small);
}
.balance span {
font-size: var(--font-size-base);
font-weight: var(--font-weight-bold);
width: 20%;
text-align: right;
font-weight: var(--font-weight-base);
font-size: var(--font-size-small);
display: inline-block;
margin-left: 0.1rem;
margin-right: 0.4rem;
}
.actions {
border-top: 1px solid var(--brand-grey-lighter);
margin-top: calc(var(--spacer) / 2);
padding-top: calc(var(--spacer) / 2);
display: flex;
justify-content: space-between;
align-items: center;
font-size: var(--font-size-small);
color: var(--color-secondary);
}
.arrow,

View File

@ -1,30 +1,49 @@
import React, { ReactElement } from 'react'
import React, { ReactElement, useEffect, useState } from 'react'
import Button from '../../atoms/Button'
import styles from './Details.module.css'
import { useWeb3, useOcean } from '@oceanprotocol/react'
import { useOcean } from '@oceanprotocol/react'
import Web3Feedback from './Feedback'
import { formatNumber } from '../../../utils'
import { connectWallet, getNetworkName } from '../../../utils/wallet'
import { getInjectedProviderName } from 'web3modal'
export default function Details({ attrs }: { attrs: any }): ReactElement {
const { balance, web3Connect } = useWeb3()
const { balanceInOcean } = useOcean()
const ethBalanceText = formatNumber(Number(balance))
const oceanBalanceText = formatNumber(Number(balanceInOcean))
const { ocean, balance, connect, logout, chainId } = useOcean()
const [balanceOcean, setBalanceOcean] = useState('0')
useEffect(() => {
async function init() {
if (!ocean) return
const accounts = await ocean.accounts.list()
const newBalanceOcean = await accounts[0].getOceanBalance()
newBalanceOcean && setBalanceOcean(newBalanceOcean)
}
init()
}, [ocean])
return (
<div className={styles.details} {...attrs}>
<ul>
<li className={styles.balance}>
OCEAN <span>{oceanBalanceText}</span>
<span>OCEAN</span> {balanceOcean}
</li>
<li className={styles.balance}>
ETH <span>{ethBalanceText}</span>
<span>ETH</span> {formatNumber(Number(balance))}
</li>
<li>
<li className={styles.actions}>
<span title="Connected provider">
{getInjectedProviderName()}
<br />
{getNetworkName(chainId)}
</span>
<Button
style="text"
size="small"
onClick={() => web3Connect.toggleModal()}
onClick={() => {
logout()
connectWallet(connect)
}}
>
Switch Wallet
</Button>

View File

@ -1,7 +1,9 @@
import React, { ReactElement } from 'react'
import Status from '../../atoms/Status'
import styles from './Feedback.module.css'
import { useWeb3, useOcean } from '@oceanprotocol/react'
import { useOcean } from '@oceanprotocol/react'
import { isCorrectNetwork, getNetworkName } from '../../../utils/wallet'
import { useSiteMetadata } from '../../../hooks/useSiteMetadata'
export declare type Web3Error = {
status: 'error' | 'warning' | 'success'
@ -14,47 +16,46 @@ export default function Web3Feedback({
}: {
isBalanceInsufficient?: boolean
}): ReactElement {
const { ethProviderStatus } = useWeb3()
const { status } = useOcean()
const isEthProviderAbsent = ethProviderStatus === -1
const isEthProviderDisconnected = ethProviderStatus === 0
const isOceanDisconnected = status === 0
const { appConfig } = useSiteMetadata()
const { account, status, chainId } = useOcean()
const isOceanConnectionError = status === -1
const hasSuccess = ethProviderStatus === 1 && status === 1
const correctNetwork = isCorrectNetwork(chainId)
const showFeedback = !account || isOceanConnectionError || !correctNetwork
const allowedNetworkNames = appConfig.networks.map((network: number) =>
getNetworkName(network)
)
const state = isEthProviderAbsent
const state = !account
? 'error'
: hasSuccess && !isBalanceInsufficient
: !correctNetwork
? 'warning'
: account && !isBalanceInsufficient
? 'success'
: 'warning'
const title = isEthProviderAbsent
? 'No Web3 Browser'
: isEthProviderDisconnected
const title = !account
? 'No account connected'
: isOceanDisconnected
? 'Not connected to Pacific network'
: isOceanConnectionError
? 'Error connecting to Ocean'
: hasSuccess
: !correctNetwork
? 'Wrong Network'
: account
? isBalanceInsufficient === true
? 'Insufficient balance'
: 'Connected to Ocean'
: 'Something went wrong'
const message = isEthProviderAbsent
? 'To download data sets you need a browser with Web3 capabilties, like Firefox with MetaMask installed.'
: isEthProviderDisconnected
const message = !account
? 'Please connect your Web3 wallet.'
: isOceanDisconnected
? 'Please connect in MetaMask to custom RPC https://pacific.oceanprotocol.com.'
: isOceanConnectionError
? 'Try again.'
? 'Please try again.'
: !correctNetwork
? `Please connect to ${allowedNetworkNames}.`
: isBalanceInsufficient === true
? 'You do not have enough OCEAN in your wallet to purchase this asset.'
: 'Something went wrong.'
return !hasSuccess ? (
return showFeedback ? (
<section className={styles.feedback}>
<Status state={state} aria-hidden />
<h3 className={styles.title}>{title}</h3>

View File

@ -1,10 +1,10 @@
import React, { ReactElement } from 'react'
import loadable from '@loadable/component'
import { useSpring, animated } from 'react-spring'
import { useWeb3 } from '@oceanprotocol/react'
import Account from './Account'
import Details from './Details'
import styles from './index.module.css'
import { useOcean } from '@oceanprotocol/react'
const Tippy = loadable(() => import('@tippyjs/react/headless'))
@ -15,7 +15,7 @@ const animation = {
}
export default function Wallet(): ReactElement {
const { account, ethProviderStatus } = useWeb3()
const { accountId } = useOcean()
const [props, setSpring] = useSpring(() => animation.from)
function onMount() {
@ -34,14 +34,12 @@ export default function Wallet(): ReactElement {
})
}
const isEthProviderAbsent = ethProviderStatus === -1
if (isEthProviderAbsent) return null
return (
<Tippy
interactive
interactiveBorder={30}
trigger="click focus"
zIndex={1}
render={(attrs: any) => (
<animated.div style={props}>
<Details attrs={attrs} />
@ -53,7 +51,7 @@ export default function Wallet(): ReactElement {
animation
onMount={onMount}
onHide={onHide}
disabled={!account}
disabled={!accountId}
fallback={<Account />}
>
<Account />

View File

@ -2,7 +2,7 @@ import React, { ReactElement } from 'react'
import Compute from './Compute'
import ddo from '../../../../tests/unit/__fixtures__/ddo'
import web3Mock from '../../../../tests/unit/__mocks__/web3'
import squidMock from '../../../../tests/unit/__mocks__/@oceanprotocol/squid'
import squidMock from '../../../../tests/unit/__mocks__/@oceanprotocol/lib'
import { context } from '../../../../tests/unit/__mocks__/web3provider'
export default {

View File

@ -1,5 +1,5 @@
import React, { useState, useEffect, ReactElement } from 'react'
import { Ocean } from '@oceanprotocol/squid'
import { Ocean } from '@oceanprotocol/lib'
import { fromWei } from 'web3-utils'
import compareAsBN, { Comparisson } from '../../../utils/compareAsBN'
import Loader from '../../atoms/Loader'

View File

@ -13,7 +13,7 @@ export default function AssetActions({
metadata: MetaDataMarket
did: string
}): ReactElement {
const { ocean, balanceInOcean } = useOcean()
// const { ocean, balanceInOcean } = useOcean()
const { access } = metadata.additionalInformation
const isCompute = access && access === 'Compute'
@ -26,12 +26,13 @@ export default function AssetActions({
<div className={styles.tabContent}>
<TabPanel>
{isCompute ? (
<Compute
did={did}
metadata={metadata}
ocean={ocean}
balance={balanceInOcean}
/>
// <Compute
// did={did}
// metadata={metadata}
// ocean={ocean}
// balance={balanceInOcean}
// />
'Compute Me'
) : (
<Consume did={did} metadata={metadata} />
)}

View File

@ -1,9 +1,6 @@
.metaFull {
margin-top: calc(var(--spacer) * 2);
font-size: var(--font-size-small);
padding: var(--spacer);
border-radius: var(--border-radius);
background: var(--brand-grey-dimmed);
display: grid;
gap: var(--spacer);
grid-template-columns: 1fr 1fr;

View File

@ -12,35 +12,14 @@ export default function MetaFull({
metadata: MetaDataMarket
}): ReactElement {
const { dateCreated, datePublished, author, license } = metadata.main
let dateRange
if (metadata && metadata.additionalInformation) {
;({ dateRange } = metadata.additionalInformation)
}
// In practice dateRange will always be defined, but in the rare case it isn't
// we put something to prevent errors
if (!dateRange) {
dateRange = [dateCreated, dateCreated]
}
const { categories } = metadata.additionalInformation
return (
<div className={styles.metaFull}>
<MetaItem title="Author" content={author} />
<MetaItem title="License" content={license} />
<MetaItem
title="Data Created"
content={
dateRange && dateRange[0] !== dateRange[1] ? (
<>
<Time date={dateRange[0]} />
{' '}
<Time date={dateRange[1]} />
</>
) : (
<Time date={dateRange[0]} />
)
}
/>
<MetaItem title="Category" content={categories[0]} />
<MetaItem title="Data Created" content={<Time date={dateCreated} />} />
<MetaItem
title="Data Published"

View File

@ -1,4 +1,4 @@
import React, { ReactElement } from 'react'
import React, { ReactElement, ReactNode } from 'react'
import styles from './MetaItem.module.css'
export default function MetaItem({
@ -6,7 +6,7 @@ export default function MetaItem({
content
}: {
title: string
content: any
content: ReactNode
}): ReactElement {
return (
<div className={styles.metaItem}>

View File

@ -12,6 +12,5 @@
}
.samples {
composes: box from '../../atoms/Box.module.css';
margin-top: calc(var(--spacer) / 2);
}

View File

@ -4,16 +4,14 @@ import { ListItem } from '../../atoms/Lists'
import MetaItem from './MetaItem'
import styles from './MetaSecondary.module.css'
import { MetaDataMarket } from '../../../@types/MetaData'
import Tags from '../../atoms/Tags'
export default function MetaSecondary({
metadata
}: {
metadata: MetaDataMarket
}): ReactElement {
let links
if (metadata && metadata.additionalInformation) {
;({ links } = metadata.additionalInformation)
}
const { links, tags } = metadata.additionalInformation
return (
<aside className={styles.metaSecondary}>
@ -33,6 +31,8 @@ export default function MetaSecondary({
/>
</div>
)}
{tags && tags.length > 0 && <Tags items={tags} />}
</aside>
)
}

View File

@ -1,25 +0,0 @@
.rating {
composes: box from '../../atoms/Box.module.css';
display: flex;
flex-wrap: wrap;
flex-direction: column;
margin-top: 5px;
padding: 20px !important;
}
.rating svg {
width: var(--font-size-h3);
height: var(--font-size-h3);
}
.title {
font-size: var(--font-size-large);
}
.success {
background-color: var(--green);
}
.error {
background-color: var(--red);
}

View File

@ -1,65 +0,0 @@
import React, { useState, useEffect, ReactElement } from 'react'
import { toast } from 'react-toastify'
import Rating from '../../atoms/Rating'
import rateAsset from '../../../utils/rateAsset'
import { DID } from '@oceanprotocol/squid'
import styles from './RatingAction.module.css'
import getAssetRating from '../../../utils/getAssetRating'
import Loader from '../../atoms/Loader'
import { useWeb3 } from '@oceanprotocol/react'
export default function RatingAction({
did,
onVote
}: {
did: DID | string
onVote: () => void
}): ReactElement {
const { web3, account } = useWeb3()
const [rating, setRating] = useState<number>(0)
const [isloading, setIsLoading] = useState(false)
useEffect(() => {
async function getOwnRating() {
if (!account) return
const currentRating = await getAssetRating(did, account)
currentRating && setRating(currentRating.vote)
}
getOwnRating()
}, [account])
async function handleRatingClick(value: number) {
if (!web3) return
setIsLoading(true)
try {
const response = await rateAsset(did, web3, value)
if (!response) return
onVote()
setRating(value)
toast.success('Thank you for rating!', {
className: styles.success
})
} catch (error) {
toast.error(`There was an error: ${error.message}`, {
className: styles.error
})
}
setIsLoading(false)
}
return account ? (
<aside className={styles.rating}>
<h3 className={styles.title}>Review this data</h3>
{isloading ? (
<Loader />
) : (
<Rating
curation={{ rating: rating, numVotes: 0 }}
isLoading={isloading}
onClick={handleRatingClick}
/>
)}
</aside>
) : null
}

View File

@ -1,13 +1,17 @@
.grid {
display: grid;
gap: calc(var(--spacer) * 1.5);
gap: calc(var(--spacer) * 2);
position: relative;
margin-top: -1.5rem;
}
.content {
composes: box from '../../atoms/Box.module.css';
margin-top: var(--spacer);
}
@media (min-width: 60rem) {
.grid {
gap: calc(var(--spacer) * 3);
/* lazy golden ratio */
grid-template-columns: 1.618fr minmax(0, 1fr);
}
@ -15,7 +19,7 @@
.sticky {
position: sticky;
top: calc(var(--spacer) / 2);
margin-top: calc(var(--spacer) * 1.5);
margin-top: var(--spacer);
}
}

View File

@ -1,10 +1,8 @@
import { MetaDataMarket } from '../../../@types/MetaData'
import React, { ReactElement } from 'react'
import { useOcean } from '@oceanprotocol/react'
import Time from '../../atoms/Time'
import { Link } from 'gatsby'
import Markdown from '../../atoms/Markdown'
import Tags from '../../atoms/Tags'
import MetaFull from './MetaFull'
import MetaSecondary from './MetaSecondary'
import styles from './index.module.css'
@ -21,42 +19,24 @@ export default function AssetContent({
did
}: AssetContentProps): ReactElement {
const { datePublished } = metadata.main
const { description, categories, tags } = metadata.additionalInformation
// const { curation } = metadata
// const { getCuration } = useMetadata()
// const [rating, setRating] = useState<number>(curation ? curation.rating : 0)
// const [numVotes, setNumVotes] = useState<number>(
// curation ? curation.numVotes : 0
// )
// const onVoteUpdate = async () => {
// const { rating, numVotes } = await getCuration(did)
// setRating(rating)
// setNumVotes(numVotes)
// }
const { description, categories } = metadata.additionalInformation
return (
<article className={styles.grid}>
<div>
<div className={styles.content}>
<aside className={styles.meta}>
<p>{datePublished && <Time date={datePublished} />}</p>
{categories && (
<p>
<Link to={`/search?categories=["${categories[0]}"]`}>
<a>{categories[0]}</a>
{categories[0]}
</Link>
</p>
)}
{/* <Rating curation={{ rating, numVotes }} readonly /> */}
</aside>
<Markdown text={description || ''} />
{tags && tags.length > 0 && <Tags items={tags} />}
<MetaSecondary metadata={metadata} />
<MetaFull did={did} metadata={metadata} />
@ -74,8 +54,6 @@ export default function AssetContent({
<div>
<div className={styles.sticky}>
<AssetActions metadata={metadata} did={did} />
{/* <RatingAction did={did} onVote={onVoteUpdate} /> */}
</div>
</div>
</article>

View File

@ -10,3 +10,9 @@
gap: var(--spacer);
}
}
.empty {
color: var(--color-secondary);
font-size: var(--font-size-small);
font-style: italic;
}

View File

@ -1,5 +1,5 @@
import React from 'react'
import { DDO } from '@oceanprotocol/squid'
import { DDO } from '@oceanprotocol/lib'
import AssetList from './AssetList'
import asset from '../../../tests/unit/__fixtures__/ddo'

View File

@ -1,64 +1,77 @@
import AssetTeaser from '../molecules/AssetTeaser'
import React from 'react'
import { QueryResult } from '@oceanprotocol/squid/dist/node/aquarius/Aquarius'
import shortid from 'shortid'
import { QueryResult } from '@oceanprotocol/lib/dist/node/metadatastore/MetadataStore'
import { useLocation, useNavigate } from '@reach/router'
import Pagination from '../molecules/Pagination'
import { updateQueryStringParameter } from '../../utils'
import styles from './AssetList.module.css'
import { MetaDataMarket } from '../../@types/MetaData'
import { DDO } from '@oceanprotocol/squid'
import { DDO } from '@oceanprotocol/lib'
import { useSiteMetadata } from '../../hooks/useSiteMetadata'
declare type AssetListProps = {
queryResult: QueryResult
}
const AssetList: React.FC<AssetListProps> = ({ queryResult }) => {
const { appConfig } = useSiteMetadata()
const location = useLocation()
const navigate = useNavigate()
// Construct the urls on the pagination links. This is only for UX,
// since the links are no <Link> they will not work by itself.
// function hrefBuilder(pageIndex: number) {
// const newUrl = updateQueryStringParameter(
// router.asPath,
// 'page',
// `${pageIndex}`
// )
// return newUrl
// }
function hrefBuilder(pageIndex: number) {
const newUrl = updateQueryStringParameter(
location.pathname + location.search,
'page',
`${pageIndex}`
)
return newUrl
}
// // This is what iniitates a new search with new `page`
// // url parameter
// function onPageChange(selected: number) {
// const newUrl = updateQueryStringParameter(
// router.asPath,
// 'page',
// `${selected + 1}`
// )
// return router.push(newUrl)
// }
function onPageChange(selected: number) {
const newUrl = updateQueryStringParameter(
location.pathname + location.search,
'page',
`${selected + 1}`
)
return navigate(newUrl)
}
return (
<>
<div className={styles.assetList}>
{queryResult &&
{queryResult && queryResult.totalResults > 0 ? (
queryResult.results.map((ddo: DDO) => {
const { attributes }: MetaDataMarket = new DDO(
ddo
).findServiceByType('metadata')
const { attributes }: MetaDataMarket = ddo.findServiceByType(
'metadata'
)
return (
<AssetTeaser
did={ddo.id}
metadata={attributes}
key={shortid.generate()}
/>
<AssetTeaser did={ddo.id} metadata={attributes} key={ddo.id} />
)
})}
})
) : (
<div className={styles.empty}>
No results found in {appConfig.oceanConfig.metadataStoreUri}
</div>
)}
</div>
{/* <Pagination
totalPages={queryResult.totalPages}
currentPage={queryResult.page}
hrefBuilder={hrefBuilder}
onPageChange={onPageChange}
/> */}
{/*
Little hack cause the pagination navigation only works
on the search page right now.
*/}
{location.pathname === '/search' && queryResult && (
<Pagination
totalPages={queryResult.totalPages}
currentPage={queryResult.page}
hrefBuilder={hrefBuilder}
onPageChange={onPageChange}
/>
)}
</>
)
}

View File

@ -10,7 +10,7 @@ import Price from '../atoms/Price'
import { fromWei } from 'web3-utils'
import DateCell from '../atoms/Table/DateCell'
import DdoLinkCell from '../atoms/Table/DdoLinkCell'
import { MetaDataMain } from '@oceanprotocol/squid'
import { MetaDataMain } from '@oceanprotocol/lib'
const consumedColumns = [
{

View File

@ -1,3 +1,2 @@
.header {
background-color: var(--brand-white);
}

View File

@ -11,7 +11,7 @@ import Price from '../atoms/Price'
import { fromWei } from 'web3-utils'
import Table from '../atoms/Table'
import Button from '../atoms/Button'
import { MetaDataMain, Logger } from '@oceanprotocol/squid'
import { MetaDataMain, Logger } from '@oceanprotocol/lib'
import DateCell from '../atoms/Table/DateCell'
import DdoLinkCell from '../atoms/Table/DdoLinkCell'
import shortid from 'shortid'

View File

@ -1,6 +1,6 @@
import React, { useEffect, useState, ReactElement } from 'react'
import Loader from '../atoms/Loader'
import { MetaDataMain } from '@oceanprotocol/squid'
import { MetaDataMain } from '@oceanprotocol/lib'
import {
useOcean,
OceanConnectionStatus,

View File

@ -15,3 +15,8 @@
top: var(--spacer);
}
}
.content {
composes: box from '../atoms/Box.module.css';
margin-top: var(--spacer);
}

View File

@ -8,15 +8,15 @@ import JobsList from '../organisms/JobsList'
const sections = [
{
title: 'Published',
component: <PublishedList />
component: 'Coming Soon...'
},
{
title: 'Downloaded',
component: <ConsumedList />
component: 'Coming Soon...'
},
{
title: 'Compute Jobs',
component: <JobsList />
component: 'Coming Soon...'
}
]
@ -32,7 +32,7 @@ const Section = ({ title, component }: { title: string; component: any }) => {
const HistoryPage: React.FC = () => {
return (
<article className={styles.grid}>
<div>
<div className={styles.content}>
{sections.map((section) => {
const { title, component } = section
return <Section key={title} title={title} component={component} />

View File

@ -1,3 +1,14 @@
.grid {
composes: assetList from '../organisms/AssetList.module.css';
.searchWrap {
display: flex;
justify-content: center;
margin-top: calc(var(--spacer) * var(--line-height));
}
.latest {
margin-top: calc(var(--spacer) * 2);
}
.latest h3 {
font-size: var(--font-size-large);
color: var(--color-secondary);
}

View File

@ -1,31 +1,60 @@
import React, { ReactElement } from 'react'
import React, { ReactElement, useEffect, useState } from 'react'
import SearchBar from '../molecules/SearchBar'
import shortid from 'shortid'
import { OceanAsset } from '../../@types/MetaData'
import AssetTeaser from '../molecules/AssetTeaser'
import styles from './Home.module.css'
import { MetadataStore, Logger } from '@oceanprotocol/lib'
import AssetList from '../organisms/AssetList'
import { QueryResult } from '@oceanprotocol/lib/dist/node/metadatastore/MetadataStore'
import Container from '../atoms/Container'
import Loader from '../atoms/Loader'
import { useSiteMetadata } from '../../hooks/useSiteMetadata'
async function getLatestAssets(metadataStoreUri: string) {
try {
const metadataStore = new MetadataStore(metadataStoreUri, Logger)
const result = await metadataStore.queryMetadata({
page: 1,
offset: 10,
query: {},
sort: { created: -1 }
})
return result
} catch (error) {
console.error(error.message)
}
}
export default function HomePage(): ReactElement {
const { appConfig } = useSiteMetadata()
const [queryResult, setQueryResult] = useState<QueryResult>()
const [loading, setLoading] = useState(true)
useEffect(() => {
async function init() {
const results = await getLatestAssets(
appConfig.oceanConfig.metadataStoreUri
)
setQueryResult(results)
setLoading(false)
}
init()
}, [])
export default function HomePage({
assets
}: {
assets: {
node: OceanAsset
}[]
}): ReactElement {
return (
<>
<SearchBar large />
{assets && (
<div className={styles.grid}>
{assets.map(({ node }: { node: OceanAsset }) => (
<AssetTeaser
key={shortid.generate()}
did={node.did}
metadata={node}
/>
))}
</div>
)}
<Container narrow className={styles.searchWrap}>
<SearchBar large />
</Container>
<section className={styles.latest}>
<h3>Latest Data Sets</h3>
{loading ? (
<Loader />
) : (
queryResult && <AssetList queryResult={queryResult} />
)}
</section>
</>
)
}

View File

@ -7,7 +7,7 @@ import { useOcean } from '@oceanprotocol/react'
import {
Service,
ServiceCompute
} from '@oceanprotocol/squid/dist/node/ddo/Service'
} from '@oceanprotocol/lib/dist/node/ddo/Service'
import { Formik, Form as FormFormik, Field } from 'formik'
import Input from '../../atoms/Input'
import Button from '../../atoms/Button'
@ -15,7 +15,7 @@ import { transformPublishFormToMetadata } from './utils'
import { FormContent, FormFieldProps } from '../../../@types/Form'
import { MetaDataPublishForm } from '../../../@types/MetaData'
import AssetModel from '../../../models/Asset'
import { File } from '@oceanprotocol/squid'
import { File } from '@oceanprotocol/lib'
const validationSchema = Yup.object().shape<MetaDataPublishForm>({
// ---- required fields ----
@ -81,7 +81,7 @@ export default function PublishForm({
// account,
// metadata.main.price,
// // Note: a hack without consequences.
// // Will make metadata.main.datePublished (automatically created by Aquarius)
// // Will make metadata.main.datePublished (automatically created by MetadataStore)
// // go out of sync with this service.main.datePublished.
// toStringNoMS(new Date(Date.now()))
// )

View File

@ -1,41 +0,0 @@
import React, { ReactElement } from 'react'
import { PageProps, graphql } from 'gatsby'
import Layout from '../Layout'
import AssetContent from '../organisms/AssetContent'
export default function AssetDetailsTemplate(props: PageProps): ReactElement {
const { oceanAsset } = props.data as any
return (
<Layout title={oceanAsset.main.name} uri={props.path}>
<AssetContent did={oceanAsset.did} metadata={oceanAsset} />
</Layout>
)
}
export const templateQuery = graphql`
query OceanAssetByDid($did: String!) {
oceanAsset(did: { eq: $did }) {
did
main {
type
name
dateCreated
author
license
price
datePublished
files {
index
contentType
}
}
additionalInformation {
description
deliveryType
termsAndConditions
access
}
}
}
`

View File

@ -1,8 +1,3 @@
.empty {
color: var(--color-secondary);
font-style: italic;
}
.grid {
display: grid;
}
@ -10,17 +5,21 @@
@media (min-width: 55rem) {
.grid {
grid-column-gap: calc(var(--spacer) * 3);
grid-template-columns: minmax(0, 1fr) 3fr;
grid-template-columns: minmax(0, 3fr) 1fr;
grid-template-areas:
'side search'
'side results';
'search side'
'results side';
}
.search {
grid-area: search;
}
.side {
grid-area: side;
margin-top: calc(var(--spacer) * 2.5);
}
.results {
grid-area: results;
}

View File

@ -1,12 +1,12 @@
import React, { ReactElement, useState, useEffect } from 'react'
import { QueryResult } from '@oceanprotocol/squid/dist/node/aquarius/Aquarius'
import { QueryResult } from '@oceanprotocol/lib/dist/node/metadatastore/MetadataStore'
import SearchBar from '../../molecules/SearchBar'
import AssetList from '../../organisms/AssetList'
import { SearchPriceFilter } from '../../molecules/SearchPriceFilter'
import styles from './index.module.css'
import queryString from 'query-string'
import { getResults } from './utils'
import Loader from '../../atoms/Loader'
export declare type SearchPageProps = {
text: string | string[]
@ -20,16 +20,19 @@ export default function SearchPage({
location: Location
}): ReactElement {
const parsed = queryString.parse(location.search)
const { text, tag } = parsed
const { text, tag, page } = parsed
const [queryResult, setQueryResult] = useState<QueryResult>()
const [loading, setLoading] = useState<boolean>()
useEffect(() => {
async function initSearch() {
setLoading(true)
const queryResult = await getResults(parsed)
setQueryResult(queryResult)
setLoading(false)
}
initSearch()
}, [parsed])
}, [text, tag, page])
return (
<section className={styles.grid}>
@ -42,11 +45,7 @@ export default function SearchPage({
</aside>
<div className={styles.results}>
{queryResult && queryResult.results.length > 0 ? (
<AssetList queryResult={queryResult} />
) : (
<div className={styles.empty}>No results found.</div>
)}
{loading ? <Loader /> : <AssetList queryResult={queryResult} />}
</div>
</section>
)

View File

@ -1,9 +1,9 @@
import {
SearchQuery,
QueryResult
} from '@oceanprotocol/squid/dist/node/aquarius/Aquarius'
} from '@oceanprotocol/lib/dist/node/metadatastore/MetadataStore'
import { priceQueryParamToWei } from '../../../utils'
import { Aquarius, Logger } from '@oceanprotocol/squid'
import { MetadataStore, Logger } from '@oceanprotocol/lib'
import { oceanConfig } from '../../../../app.config'
export function getSearchQuery(
@ -52,8 +52,8 @@ export async function getResults(params: any): Promise<QueryResult> {
])
: undefined
const aquarius = new Aquarius(oceanConfig.aquariusUri, Logger)
const queryResult = await aquarius.queryMetadata(
const metadataStore = new MetadataStore(oceanConfig.metadataStoreUri, Logger)
const queryResult = await metadataStore.queryMetadata(
getSearchQuery(page, offset, text, tag, priceQuery)
)

View File

@ -3,7 +3,6 @@ import { ToastContainer } from 'react-toastify'
import '@oceanprotocol/typographies/css/ocean-typo.css'
import '../global/styles.css'
import 'react-toastify/dist/ReactToastify.css'
export default function Styles({
children

View File

@ -1,3 +1,5 @@
@import '../../node_modules/react-toastify/dist/ReactToastify.css';
div.Toastify__toast {
font-family: var(--font-family-base);
font-size: var(--font-size-small);

68
src/global/_web3modal.css Normal file
View File

@ -0,0 +1,68 @@
div.web3modal-modal-lightbox {
background: rgba(255, 255, 255, 0.8);
backdrop-filter: blur(3px);
overflow-x: hidden;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
animation: fadeIn 0.2s ease-out backwards;
}
div.web3modal-modal-card {
border-radius: var(--border-radius);
padding: var(--spacer);
margin: var(--spacer) auto;
max-width: var(--break-point--small);
border: 1px solid var(--brand-grey-lighter);
box-shadow: 0 6px 15px 0 rgba(0, 0, 0, 0.05);
animation: moveUp 0.2s ease-out backwards;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
}
div.web3modal-provider-wrapper {
border: none;
padding: 0;
}
div.web3modal-provider-container {
padding: var(--spacer);
}
div.web3modal-provider-icon {
filter: grayscale(1) contrast(150%);
transition: filter 0.2s ease-out;
}
div.web3modal-provider-wrapper:hover div.web3modal-provider-icon {
filter: none;
}
div.web3modal-provider-name {
font-size: var(--font-size-large);
font-weight: var(--font-weight-bold);
font-family: var(--font-family-title);
}
div.web3modal-provider-description {
font-size: var(--font-size-base);
margin-top: 0;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes moveUp {
from {
transform: translate3d(0, 1rem, 0);
}
to {
transform: translate3d(0, 0, 0);
}
}

View File

@ -134,3 +134,4 @@ fieldset {
@import '_code.css';
@import '_toast.css';
@import '_web3modal.css';

View File

@ -1,5 +1,5 @@
import React, { ReactElement } from 'react'
import { Web3Provider, OceanProvider, Config } from '@oceanprotocol/react'
import { OceanProvider } from '@oceanprotocol/react'
import { oceanConfig } from '../../app.config'
const wrapRootElement = ({
@ -7,9 +7,7 @@ const wrapRootElement = ({
}: {
element: ReactElement
}): ReactElement => (
<Web3Provider>
<OceanProvider config={oceanConfig as Config}>{element}</OceanProvider>
</Web3Provider>
<OceanProvider config={oceanConfig}>{element}</OceanProvider>
)
export default wrapRootElement

View File

@ -2,15 +2,36 @@ import { useStaticQuery, graphql } from 'gatsby'
const query = graphql`
query {
siteMetadata: allFile(filter: { relativePath: { eq: "site.json" } }) {
site {
siteMetadata {
siteTitle
siteTagline
siteUrl
siteIcon
copyright
menu {
name
link
}
appConfig {
infuraProjectId
networks
oceanConfig {
factoryAddress
metadataStoreUri
nodeUri
providerUri
verbose
}
}
}
}
siteImage: allFile(filter: { relativePath: { eq: "site.json" } }) {
edges {
node {
childContentJson {
site {
siteTitle
siteTagline
siteUrl
siteIcon
siteImage {
childImageSharp {
original {
@ -18,11 +39,6 @@ const query = graphql`
}
}
}
copyright
menu {
name
link
}
}
}
}
@ -33,5 +49,11 @@ const query = graphql`
export function useSiteMetadata() {
const data = useStaticQuery(query)
return data.siteMetadata.edges[0].node.childContentJson.site
const siteMeta = {
...data.siteImage.edges[0].node.childContentJson.site,
...data.site.siteMetadata
}
return siteMeta
}

View File

@ -4,11 +4,13 @@ import AssetContent from '../../components/organisms/AssetContent'
import Layout from '../../components/Layout'
import { PageProps } from 'gatsby'
import { MetaDataMarket, ServiceMetaDataMarket } from '../../@types/MetaData'
import { Aquarius, Logger } from '@oceanprotocol/squid'
import { oceanConfig } from '../../../app.config'
import { MetadataStore, Logger } from '@oceanprotocol/lib'
import Alert from '../../components/atoms/Alert'
import Loader from '../../components/atoms/Loader'
import { useSiteMetadata } from '../../hooks/useSiteMetadata'
export default function AssetRoute(props: PageProps): ReactElement {
const { appConfig } = useSiteMetadata()
const [metadata, setMetadata] = useState<MetaDataMarket>()
const [title, setTitle] = useState<string>()
const [error, setError] = useState<string>()
@ -18,12 +20,15 @@ export default function AssetRoute(props: PageProps): ReactElement {
useEffect(() => {
async function init() {
try {
const aquarius = new Aquarius(oceanConfig.aquariusUri, Logger)
const ddo = await aquarius.retrieveDDO(did)
const metadataStore = new MetadataStore(
appConfig.oceanConfig.metadataStoreUri,
Logger
)
const ddo = await metadataStore.retrieveDDO(did)
if (!ddo) {
setTitle('Could not retrieve asset')
setError('The DDO was not found in Aquarius.')
setError('The DDO was not found in MetadataStore.')
return
}
@ -39,21 +44,25 @@ export default function AssetRoute(props: PageProps): ReactElement {
}
}
init()
}, [])
}, [did])
return error ? (
return did && metadata ? (
<Layout title={title} uri={props.location.pathname}>
<Router basepath="/asset">
<AssetContent
did={did}
metadata={metadata as MetaDataMarket}
path=":did"
/>
</Router>
</Layout>
) : error ? (
<Layout title={title} noPageHeader uri={props.location.pathname}>
<Alert title={title} text={error} state="error" />
</Layout>
) : did && metadata ? (
<Layout title={title} uri={props.location.pathname}>
<Router basepath="/asset">
<AssetContent did={did} metadata={metadata} path="/asset/:did" />
</Router>
</Layout>
) : (
<Layout title="Loading..." uri={props.location.pathname}>
Loading...
<Loader />
</Layout>
)
}

View File

@ -1,40 +1,20 @@
import React, { ReactElement } from 'react'
import { PageProps, graphql } from 'gatsby'
import { PageProps } from 'gatsby'
import PageHome from '../components/pages/Home'
import { useSiteMetadata } from '../hooks/useSiteMetadata'
import Layout from '../components/Layout'
export default function PageGatsbyHome(props: PageProps): ReactElement {
const { siteTitle, siteTagline } = useSiteMetadata()
const assets = (props.data as any).allOceanAsset.edges
return (
<Layout title={siteTitle} description={siteTagline} uri={props.uri}>
<PageHome assets={assets} />
<Layout
title={siteTitle}
description={siteTagline}
uri={props.uri}
headerCenter
>
<PageHome />
</Layout>
)
}
export const pageQuery = graphql`
query PageHomeQuery {
allOceanAsset {
edges {
node {
did
main {
type
name
dateCreated
author
price
datePublished
}
additionalInformation {
description
access
}
}
}
}
}
`

View File

@ -1,34 +0,0 @@
import axios from 'axios'
import { DID } from '@oceanprotocol/squid'
import { oceanConfig } from '../../app.config'
export declare type GetRatingResponse = {
comment: string
datePublished: string
vote: number
}
const url = oceanConfig.ratingUri + '/api/v1/rating'
export default async function getAssetRating(
did: DID | string,
account: string
): Promise<GetRatingResponse | undefined> {
try {
if (!account) return
const response = await axios.get(url, {
params: {
did: did,
address: account
}
})
const votesLength = response.data.length
if (votesLength > 0) {
return response.data[votesLength - 1]
}
} catch (error) {
console.error(error.message)
}
}

View File

@ -1,6 +1,6 @@
import axios, { AxiosResponse } from 'axios'
import { toast } from 'react-toastify'
import { File } from '@oceanprotocol/squid'
import { File } from '@oceanprotocol/lib'
import numeral from 'numeral'
import web3Utils from 'web3-utils'

View File

@ -1,49 +0,0 @@
import axios, { AxiosResponse } from 'axios'
import Web3 from 'web3'
import { DID } from '@oceanprotocol/squid'
import { oceanConfig } from '../../app.config'
export declare type RatingResponse = [string, number]
const url = oceanConfig.ratingUri + '/api/v1/rating'
export function gethash(message: string) {
let hex = ''
for (let i = 0; i < message.length; i++) {
hex += '' + message.charCodeAt(i).toString(16)
}
const hexMessage = '0x' + hex
return hexMessage
}
export default async function rateAsset(
did: DID | string,
web3: Web3,
value: number
): Promise<RatingResponse | string> {
try {
const timestamp = Math.floor(+new Date() / 1000)
const accounts = await web3.eth.getAccounts()
const signature = await web3.eth.personal.sign(
gethash(`${timestamp}`),
accounts ? accounts[0] : '',
''
)
const ratingBody = {
did,
vote: value,
comment: '',
address: accounts[0],
timestamp: timestamp,
signature: signature
}
const response: AxiosResponse = await axios.post(url, ratingBody)
if (!response) return 'No Response'
return response.data
} catch (error) {
console.error(error.message)
return `Error: ${error.message}`
}
}

55
src/utils/wallet.ts Normal file
View File

@ -0,0 +1,55 @@
import { OceanProviderValue } from '@oceanprotocol/react'
import atlas from '@ethereum-navigator/atlas'
import { networks, infuraProjectId } from '../../app.config'
const web3ModalTheme = {
background: 'var(--brand-white)',
main: 'var(--brand-black)',
secondary: 'var(--brand-grey-light)',
border: 'var(--brand-grey-lighter)',
hover: 'var(--brand-grey-dimmed)'
}
export async function connectWallet(
connect: OceanProviderValue['connect']
): Promise<void> {
const { default: WalletConnectProvider } = await import(
'@walletconnect/web3-provider'
)
const providerOptions = {
/* See Provider Options Section */
walletconnect: {
package: WalletConnectProvider, // required
options: {
infuraId: infuraProjectId // required
}
}
}
await connect({ cacheProvider: true, providerOptions, theme: web3ModalTheme })
}
export function isCorrectNetwork(chainId: number): boolean {
const allowedIds = networks
return allowedIds.includes(chainId)
}
export function accountTruncate(account: string): string {
const middle = account.substring(6, 38)
const truncated = account.replace(middle, '…')
return truncated
}
export function getNetworkName(chainId: number): string {
switch (chainId) {
case 1:
return 'Main'
case 4:
return 'Rinkeby'
case 42:
return 'Kovan'
default:
return 'Unknown'
}
}

View File

@ -1,4 +1,4 @@
import { DDO } from '@oceanprotocol/squid'
import { DDO } from '@oceanprotocol/lib'
import { MetaDataMarket } from '../../../src/@types/MetaData'
const ddo: Partial<DDO> = {

View File

@ -1,4 +1,4 @@
import { ComputeJob } from '@oceanprotocol/squid'
import { ComputeJob } from '@oceanprotocol/lib'
// ComputeJob need to be updated in squid
const job: Partial<ComputeJob> = {

View File

@ -1,7 +1,7 @@
import ddo from '../../__fixtures__/ddo'
import job from '../../__fixtures__/job'
const aquarius = {
const metadataStore = {
queryMetadata: () => {
return {
results: [] as any[],
@ -12,13 +12,13 @@ const aquarius = {
}
const squidMock = {
Aquarius: () => aquarius,
MetadataStore: () => metadataStore,
DDO: () => ddo,
ocean: {
accounts: {
list: () => ['xxx', 'xxx']
},
aquarius,
metadataStore,
compute: {
status: (account: string) => {
return [job]
@ -69,13 +69,13 @@ const squidMock = {
name: 'Squid-js',
status: 'Working'
},
aquarius: {
name: 'Aquarius',
metadataStore: {
name: 'MetadataStore',
status: 'Working'
},
brizo: {
name: 'Brizo',
network: 'Nile',
provider: {
name: 'Provider',
network: 'Rinkeby',
status: 'Working',
contracts: {
hello: 'hello',

View File

@ -1,5 +1,5 @@
import React, { ReactElement } from 'react'
import squidMock from './squid'
import libMock from './lib'
import web3ProviderMock from '../web3provider'
const reactMock = {
@ -19,7 +19,7 @@ const reactMock = {
},
useOcean: () => {
return {
ocean: squidMock.ocean
ocean: libMock.ocean
}
},
useWeb3: () => {

View File

@ -1,10 +0,0 @@
import React from 'react'
import { render } from '@testing-library/react'
import { Web3Provider } from '@oceanprotocol/react'
describe('Web3Provider', () => {
it('renders without crashing', () => {
const { container } = render(<Web3Provider>Children</Web3Provider>)
expect(container).toHaveTextContent('Children')
})
})

View File

@ -1,23 +0,0 @@
import axios, { AxiosResponse } from 'axios'
import getAssetRating, {
GetRatingResponse
} from '../../../src/utils/getAssetRating'
jest.mock('axios')
describe('getAssetRating()', () => {
it('success', async () => {
const ratingResponse: GetRatingResponse = {
comment: '',
datePublished: '',
vote: 5
}
;(axios.get as any).mockResolvedValueOnce({
data: [ratingResponse, ratingResponse]
} as AxiosResponse)
const response = await getAssetRating('0x00', '0x00')
expect(response && response.vote).toBe(5)
})
})

View File

@ -1,38 +0,0 @@
import axios, { AxiosResponse } from 'axios'
import rateAsset, { RatingResponse } from '../../../src/utils/rateAsset'
import web3 from '../__mocks__/web3'
jest.mock('axios')
describe('rateAsset()', () => {
it('success', async () => {
;(axios.post as any).mockResolvedValueOnce({
data: ['4.0', 1]
} as AxiosResponse)
const response: RatingResponse | string = await rateAsset('0x00', web3, 5)
expect(response && response[0]).toBe('4.0')
})
it('string return', async () => {
;(axios.post as any).mockResolvedValueOnce({
data: 'Missing signature'
} as AxiosResponse)
const response: RatingResponse | string = await rateAsset('0x00', web3, 5)
expect(response && response).toBe('Missing signature')
})
it('error catch', async () => {
;(axios.post as any).mockResolvedValueOnce({
data: {}
} as AxiosResponse)
const response: RatingResponse | string = await rateAsset(
'0x00',
{} as any,
5
)
expect(response && response).toContain('Error: ')
})
})