1
0
mirror of https://github.com/oceanprotocol/market.git synced 2024-11-16 02:04:54 +01:00

Merge branch 'main' into feature/consume

This commit is contained in:
mihaisc 2020-08-05 15:21:31 +03:00
commit 9cd441c483
19 changed files with 101 additions and 40 deletions

5
package-lock.json generated
View File

@ -1246,6 +1246,11 @@
"minimist": "^1.2.0" "minimist": "^1.2.0"
} }
}, },
"@coingecko/cryptoformat": {
"version": "0.3.8",
"resolved": "https://registry.npmjs.org/@coingecko/cryptoformat/-/cryptoformat-0.3.8.tgz",
"integrity": "sha512-nmHLzakZyFCo49qH8q19PbJIt16j2GQdPvjPZ5+roqj/y8KTDsFG9BrYvm7fpHN1xukN79XPhNMqBQN2RdZNQw=="
},
"@emotion/cache": { "@emotion/cache": {
"version": "10.0.29", "version": "10.0.29",
"resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-10.0.29.tgz", "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-10.0.29.tgz",

View File

@ -19,6 +19,7 @@
"storybook:build": "build-storybook -c .storybook -o public/storybook" "storybook:build": "build-storybook -c .storybook -o public/storybook"
}, },
"dependencies": { "dependencies": {
"@coingecko/cryptoformat": "^0.3.8",
"@loadable/component": "^5.13.1", "@loadable/component": "^5.13.1",
"@oceanprotocol/art": "^3.0.0", "@oceanprotocol/art": "^3.0.0",
"@oceanprotocol/lib": "^0.1.10", "@oceanprotocol/lib": "^0.1.10",

View File

@ -22,8 +22,8 @@ export interface MetadataPublishForm {
author: string author: string
license: string license: string
price: { price: {
cost: number
tokensToMint: number tokensToMint: number
type: 'simple' | 'advanced' | string
} }
access: 'Download' | 'Compute' | string access: 'Download' | 'Compute' | string
termsAndConditions: boolean termsAndConditions: boolean

View File

@ -14,6 +14,7 @@
} }
.message { .message {
font-weight: var(--font-weight-base);
font-size: var(--font-size-small); font-size: var(--font-size-small);
color: var(--brand-grey-light); color: var(--brand-grey-light);
display: block; display: block;

View File

@ -3,6 +3,7 @@ import useSWR from 'swr'
import { fetchData, isBrowser } from '../../../utils' import { fetchData, isBrowser } from '../../../utils'
import styles from './Conversion.module.css' import styles from './Conversion.module.css'
import classNames from 'classnames/bind' import classNames from 'classnames/bind'
import { formatCurrency } from '@coingecko/cryptoformat'
const cx = classNames.bind(styles) const cx = classNames.bind(styles)
@ -34,7 +35,7 @@ export default function Conversion({
const { eur } = data['ocean-protocol'] const { eur } = data['ocean-protocol']
const converted = eur * Number(price) const converted = eur * Number(price)
setPriceEur(`${converted.toFixed(2)}`) setPriceEur(`${formatCurrency(converted, 'EUR', 'en', true)}`)
} }
useEffect(() => { useEffect(() => {

View File

@ -12,6 +12,6 @@
.small { .small {
/* lazy making-conversion-smaller-with-same-markup */ /* lazy making-conversion-smaller-with-same-markup */
transform: scale(0.7); transform: scale(0.8);
transform-origin: left 80%; transform-origin: left 80%;
} }

View File

@ -1,8 +1,8 @@
import React, { ReactElement } from 'react' import React, { ReactElement } from 'react'
import { fromWei } from 'web3-utils'
import classNames from 'classnames/bind' import classNames from 'classnames/bind'
import PriceConversion from './Conversion' import PriceConversion from './Conversion'
import styles from './index.module.css' import styles from './index.module.css'
import { formatCurrency } from '@coingecko/cryptoformat'
const cx = classNames.bind(styles) const cx = classNames.bind(styles)
@ -11,7 +11,7 @@ export default function Price({
className, className,
small small
}: { }: {
price: string price: string // expects price in OCEAN, not wei
className?: string className?: string
small?: boolean small?: boolean
}): ReactElement { }): ReactElement {
@ -27,7 +27,7 @@ export default function Price({
'Free' 'Free'
) : ( ) : (
<> <>
<span>OCEAN</span> {price} <span>OCEAN</span> {formatCurrency(Number(price), '', 'en', false, true)}
<PriceConversion price={price} /> <PriceConversion price={price} />
</> </>
) )

View File

@ -9,16 +9,22 @@ interface TabsItem {
export default function Tabs({ export default function Tabs({
items, items,
className className,
handleTabChange
}: { }: {
items: TabsItem[] items: TabsItem[]
className?: string className?: string
handleTabChange?: (tabName: string) => void
}): ReactElement { }): ReactElement {
return ( return (
<ReactTabs className={`${className && className}`}> <ReactTabs className={`${className && className}`}>
<TabList className={styles.tabList}> <TabList className={styles.tabList}>
{items.map((item) => ( {items.map((item) => (
<Tab className={styles.tab} key={item.title}> <Tab
className={styles.tab}
key={item.title}
onClick={() => handleTabChange(item.title)}
>
{item.title} {item.title}
</Tab> </Tab>
))} ))}

View File

@ -6,6 +6,7 @@ import Price from '../atoms/Price'
import styles from './AssetTeaser.module.css' import styles from './AssetTeaser.module.css'
import { useMetadata } from '@oceanprotocol/react' import { useMetadata } from '@oceanprotocol/react'
import { DDO } from '@oceanprotocol/lib' import { DDO } from '@oceanprotocol/lib'
import Loader from '../atoms/Loader'
declare type AssetTeaserProps = { declare type AssetTeaserProps = {
ddo: DDO ddo: DDO
@ -20,6 +21,7 @@ const AssetTeaser: React.FC<AssetTeaserProps> = ({
const { name } = metadata.main const { name } = metadata.main
const { description } = metadata.additionalInformation const { description } = metadata.additionalInformation
const isCompute = Boolean(ddo.findServiceByType('compute'))
const { getBestPrice } = useMetadata(ddo.id) const { getBestPrice } = useMetadata(ddo.id)
const [price, setPrice] = useState<string>() const [price, setPrice] = useState<string>()
@ -36,9 +38,7 @@ const AssetTeaser: React.FC<AssetTeaserProps> = ({
<article className={styles.teaser}> <article className={styles.teaser}>
<Link to={`/asset/${ddo.id}`} className={styles.link}> <Link to={`/asset/${ddo.id}`} className={styles.link}>
<h1 className={styles.title}>{name}</h1> <h1 className={styles.title}>{name}</h1>
{/* {access === 'Compute' && ( {isCompute && <div className={styles.accessLabel}>Compute</div>}
<div className={styles.accessLabel}>{access}</div>
)} */}
<div className={styles.content}> <div className={styles.content}>
<Dotdotdot tagName="p" clamp={3}> <Dotdotdot tagName="p" clamp={3}>
@ -47,7 +47,11 @@ const AssetTeaser: React.FC<AssetTeaserProps> = ({
</div> </div>
<footer className={styles.foot}> <footer className={styles.foot}>
{price && <Price price={price} />} {price ? (
<Price price={price} small />
) : (
<Loader message="Retrieving price..." />
)}
</footer> </footer>
</Link> </Link>
</article> </article>

View File

@ -21,7 +21,7 @@ export default function Advanced({
onChange: (event: ChangeEvent<HTMLInputElement>) => void onChange: (event: ChangeEvent<HTMLInputElement>) => void
}): ReactElement { }): ReactElement {
const { appConfig } = useSiteMetadata() const { appConfig } = useSiteMetadata()
const { account, balance, chainId } = useOcean() const { account, balance, chainId, refreshBalance } = useOcean()
const [error, setError] = useState<string>() const [error, setError] = useState<string>()
const correctNetwork = isCorrectNetwork(chainId) const correctNetwork = isCorrectNetwork(chainId)
@ -42,6 +42,17 @@ export default function Advanced({
} }
}, [ocean]) }, [ocean])
// refetch balance periodically
useEffect(() => {
if (!account) return
const balanceInterval = setInterval(() => refreshBalance(), 10000) // 10 sec.
return () => {
clearInterval(balanceInterval)
}
}, [])
return ( return (
<div className={stylesIndex.content}> <div className={stylesIndex.content}>
<div className={styles.advanced}> <div className={styles.advanced}>

View File

@ -9,7 +9,6 @@ import { useField } from 'formik'
export default function Price(props: InputProps): ReactElement { export default function Price(props: InputProps): ReactElement {
const [field, meta, helpers] = useField(props) const [field, meta, helpers] = useField(props)
const cost = 1
const weightOnDataToken = '90' // in % const weightOnDataToken = '90' // in %
const [ocean, setOcean] = useState('1') const [ocean, setOcean] = useState('1')
const [tokensToMint, setTokensToMint] = useState<number>() const [tokensToMint, setTokensToMint] = useState<number>()
@ -18,11 +17,17 @@ export default function Price(props: InputProps): ReactElement {
setOcean(event.target.value) setOcean(event.target.value)
} }
function handleTabChange(tabName: string) {
const type = tabName.startsWith('Simple') ? 'simple' : 'advanced'
helpers.setValue({ ...field.value, type })
}
// Always update everything when ocean changes // Always update everything when ocean changes
useEffect(() => { useEffect(() => {
const tokensToMint = Number(ocean) * (Number(weightOnDataToken) / 10) const tokensToMint = Number(ocean) * (Number(weightOnDataToken) / 10)
setTokensToMint(tokensToMint) setTokensToMint(tokensToMint)
helpers.setValue({ cost, tokensToMint }) console.log(field.value)
helpers.setValue({ ...field.value, tokensToMint })
}, [ocean]) }, [ocean])
const tabs = [ const tabs = [
@ -45,7 +50,7 @@ export default function Price(props: InputProps): ReactElement {
return ( return (
<div className={styles.price}> <div className={styles.price}>
<Tabs items={tabs} /> <Tabs items={tabs} handleTabChange={handleTabChange} />
<pre> <pre>
<code>{JSON.stringify(field.value)}</code> <code>{JSON.stringify(field.value)}</code>
</pre> </pre>

View File

@ -6,6 +6,7 @@ import Web3Feedback from './Feedback'
import { getNetworkName } from '../../../utils/wallet' import { getNetworkName } from '../../../utils/wallet'
import { getInjectedProviderName } from 'web3modal' import { getInjectedProviderName } from 'web3modal'
import Conversion from '../../atoms/Price/Conversion' import Conversion from '../../atoms/Price/Conversion'
import { formatCurrency } from '@coingecko/cryptoformat'
export default function Details({ attrs }: { attrs: any }): ReactElement { export default function Details({ attrs }: { attrs: any }): ReactElement {
const { balance, connect, logout, chainId } = useOcean() const { balance, connect, logout, chainId } = useOcean()
@ -15,7 +16,8 @@ export default function Details({ attrs }: { attrs: any }): ReactElement {
<ul> <ul>
{Object.entries(balance).map(([key, value]) => ( {Object.entries(balance).map(([key, value]) => (
<li className={styles.balance} key={key}> <li className={styles.balance} key={key}>
<span>{key.toUpperCase()}</span> {value} <span>{key.toUpperCase()}</span>{' '}
{formatCurrency(value, '', 'en', true, true)}
{key === 'ocean' && <Conversion price={value} />} {key === 'ocean' && <Conversion price={value} />}
</li> </li>
))} ))}

View File

@ -14,10 +14,13 @@ const animation = {
} }
export default function Wallet(): ReactElement { export default function Wallet(): ReactElement {
const { accountId } = useOcean() const { accountId, refreshBalance } = useOcean()
const [props, setSpring] = useSpring(() => animation.from) const [props, setSpring] = useSpring(() => animation.from)
// always refetch balance when popover is opened
function onMount() { function onMount() {
accountId && refreshBalance()
setSpring({ setSpring({
transform: 'scale(1) translateY(0)', transform: 'scale(1) translateY(0)',
onRest: (): void => null, onRest: (): void => null,

View File

@ -1,6 +1,5 @@
import React, { useState, useEffect, ReactElement } from 'react' import React, { useState, useEffect, ReactElement } from 'react'
import { DDO } from '@oceanprotocol/lib' import { DDO } from '@oceanprotocol/lib'
import { fromWei } from 'web3-utils'
import compareAsBN, { Comparisson } from '../../../utils/compareAsBN' import compareAsBN, { Comparisson } from '../../../utils/compareAsBN'
import Loader from '../../atoms/Loader' import Loader from '../../atoms/Loader'
import Web3Feedback from '../../molecules/Wallet/Feedback' import Web3Feedback from '../../molecules/Wallet/Feedback'
@ -17,7 +16,13 @@ import Button from '../../atoms/Button'
import Input from '../../atoms/Input' import Input from '../../atoms/Input'
import Alert from '../../atoms/Alert' import Alert from '../../atoms/Alert'
export default function Compute({ ddo }: { ddo: DDO }): ReactElement { export default function Compute({
ddo,
price
}: {
ddo: DDO
price: string // in OCEAN, not wei
}): ReactElement {
const { ocean } = useOcean() const { ocean } = useOcean()
const { compute, isLoading, computeStepText, computeError } = useCompute() const { compute, isLoading, computeStepText, computeError } = useCompute()
const computeService = ddo.findServiceByType('compute').attributes.main const computeService = ddo.findServiceByType('compute').attributes.main
@ -36,7 +41,7 @@ export default function Compute({ ddo }: { ddo: DDO }): ReactElement {
const [file, setFile] = useState(null) const [file, setFile] = useState(null)
const [isTermsAgreed, setIsTermsAgreed] = useState(true) const [isTermsAgreed, setIsTermsAgreed] = useState(true)
const isFree = computeService.cost === '0' const isFree = price === '0'
const isComputeButtonDisabled = const isComputeButtonDisabled =
isJobStarting || isJobStarting ||
@ -96,7 +101,11 @@ export default function Compute({ ddo }: { ddo: DDO }): ReactElement {
return ( return (
<div className={styles.compute}> <div className={styles.compute}>
<Price price={computeService.cost} /> {price ? (
<Price price={price} />
) : (
<Loader message="Retrieving price..." />
)}
<div className={styles.info}> <div className={styles.info}>
<div className={styles.selectType}> <div className={styles.selectType}>

View File

@ -1,5 +1,4 @@
import React, { ReactElement } from 'react' import React, { ReactElement } from 'react'
import { fromWei } from 'web3-utils'
import { toast } from 'react-toastify' import { toast } from 'react-toastify'
import { File as FileMetadata, DDO } from '@oceanprotocol/lib' import { File as FileMetadata, DDO } from '@oceanprotocol/lib'
import compareAsBN, { Comparisson } from '../../../utils/compareAsBN' import compareAsBN, { Comparisson } from '../../../utils/compareAsBN'
@ -13,17 +12,18 @@ import { useOcean, useConsume } from '@oceanprotocol/react'
export default function Consume({ export default function Consume({
ddo, ddo,
price,
file file
}: { }: {
ddo: DDO ddo: DDO
price: string // in OCEAN, not wei
file: FileMetadata file: FileMetadata
}): ReactElement { }): ReactElement {
const accessService = ddo.findServiceByType('access') const accessService = ddo.findServiceByType('access')
const { cost } = accessService.attributes.main
const { ocean } = useOcean() const { ocean } = useOcean()
const { consumeStepText, consume, consumeError } = useConsume() const { consumeStepText, consume, consumeError } = useConsume()
const isFree = cost === '0' const isFree = price === '0'
// const isBalanceSufficient = // const isBalanceSufficient =
// isFree || compareAsBN(balanceInOcean, fromWei(cost), Comparisson.gte) // isFree || compareAsBN(balanceInOcean, fromWei(cost), Comparisson.gte)
const isDisabled = !ocean const isDisabled = !ocean
@ -52,7 +52,11 @@ export default function Consume({
<File file={file} /> <File file={file} />
</div> </div>
<div className={styles.pricewrapper}> <div className={styles.pricewrapper}>
{/* <Price price={cost} className={styles.price} /> */} {price ? (
<Price price={price} />
) : (
<Loader message="Retrieving price..." />
)}
<PurchaseButton /> <PurchaseButton />
</div> </div>
</div> </div>

View File

@ -1,10 +1,11 @@
import React, { ReactElement } from 'react' import React, { ReactElement, useState, useEffect } from 'react'
import styles from './index.module.css' import styles from './index.module.css'
import Compute from './Compute' import Compute from './Compute'
import Consume from './Consume' import Consume from './Consume'
import { MetadataMarket } from '../../../@types/Metadata' import { MetadataMarket } from '../../../@types/Metadata'
import { DDO } from '@oceanprotocol/lib' import { DDO } from '@oceanprotocol/lib'
import Tabs from '../../atoms/Tabs' import Tabs from '../../atoms/Tabs'
import { useMetadata } from '@oceanprotocol/react'
export default function AssetActions({ export default function AssetActions({
metadata, metadata,
@ -13,12 +14,22 @@ export default function AssetActions({
metadata: MetadataMarket metadata: MetadataMarket
ddo: DDO ddo: DDO
}): ReactElement { }): ReactElement {
const { access } = metadata.additionalInformation const { getBestPrice } = useMetadata(ddo.id)
const isCompute = access && access === 'Compute' const [price, setPrice] = useState<string>()
useEffect(() => {
async function init() {
const price = await getBestPrice(ddo.dataToken)
price && setPrice(price)
}
init()
}, [])
const isCompute = Boolean(ddo.findServiceByType('compute'))
const UseContent = isCompute ? ( const UseContent = isCompute ? (
<Compute ddo={ddo} /> <Compute ddo={ddo} price={price} />
) : ( ) : (
<Consume ddo={ddo} file={metadata.main.files[0]} /> <Consume ddo={ddo} price={price} file={metadata.main.files[0]} />
) )
const tabs = [ const tabs = [

View File

@ -29,14 +29,13 @@ export default function PublishPage({
`) `)
const metadata = transformPublishFormToMetadata(values) const metadata = transformPublishFormToMetadata(values)
const { cost, tokensToMint } = values.price const { tokensToMint, type } = values.price
const serviceType = values.access === 'Download' ? 'access' : 'compute' const serviceType = values.access === 'Download' ? 'access' : 'compute'
console.log(` console.log(`
Transformed metadata values: Transformed metadata values:
---------------------- ----------------------
${JSON.stringify(metadata, null, 2)} ${JSON.stringify(metadata, null, 2)}
Cost: ${cost}
Tokens to mint: ${tokensToMint} Tokens to mint: ${tokensToMint}
`) `)

View File

@ -22,14 +22,11 @@ pre {
max-height: 800px; max-height: 800px;
width: 100%; width: 100%;
position: relative; position: relative;
white-space: pre-wrap;
} }
pre code { pre code {
padding: 0; padding: 0;
white-space: pre;
display: block; display: block;
overflow-wrap: normal;
word-wrap: normal;
word-break: normal;
width: 100%; width: 100%;
} }

View File

@ -7,8 +7,10 @@ export const validationSchema = Yup.object().shape<MetadataPublishForm>({
name: Yup.string().required('Required'), name: Yup.string().required('Required'),
author: Yup.string().required('Required'), author: Yup.string().required('Required'),
price: Yup.object().shape({ price: Yup.object().shape({
cost: Yup.number().required('Required'), tokensToMint: Yup.number().required('Required'),
tokensToMint: Yup.number().required('Required') type: Yup.string()
.matches(/simple|advanced/g)
.required('Required')
}), }),
files: Yup.array<FileMetadata>().required('Required').nullable(), files: Yup.array<FileMetadata>().required('Required').nullable(),
description: Yup.string().required('Required'), description: Yup.string().required('Required'),
@ -28,7 +30,7 @@ export const initialValues: MetadataPublishForm = {
name: undefined, name: undefined,
author: undefined, author: undefined,
price: { price: {
cost: 1, type: 'simple',
tokensToMint: 1 tokensToMint: 1
}, },
files: undefined, files: undefined,