mirror of
https://github.com/oceanprotocol/market.git
synced 2024-12-02 05:57:29 +01:00
Merge pull request #112 from oceanprotocol/feature/flow-splits
Split consume/compute/publish flows
This commit is contained in:
commit
b67dce4252
@ -42,9 +42,9 @@
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "price",
|
||||
"label": "Price",
|
||||
"type": "price",
|
||||
"name": "dataTokenOptions",
|
||||
"label": "Datatoken",
|
||||
"type": "datatoken",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
@ -96,21 +96,5 @@
|
||||
}
|
||||
],
|
||||
"success": "Asset Created!"
|
||||
},
|
||||
"price": {
|
||||
"fixed": {
|
||||
"title": "Fixed",
|
||||
"info": "Set your price for accessing this data set. A Datatoken for this data set, worth the entered amount of OCEAN, will be created."
|
||||
},
|
||||
"dynamic": {
|
||||
"title": "Dynamic",
|
||||
"info": "Let's create a decentralized, automated market for your data set. A Datatoken for this data set, worth the entered amount of OCEAN, will be created. Additionally, you will provide liquidity into a OCEAN/Datatoken liquidity pool with Balancer.",
|
||||
"tooltips": {
|
||||
"poolInfo": "Explain what is going on here...",
|
||||
"swapFee": "Explain liquidity provider fee...",
|
||||
"communityFee": "Explain community fee...",
|
||||
"marketplaceFee": "Explain marketplace fee..."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,28 @@
|
||||
{
|
||||
"create": {
|
||||
"empty": {
|
||||
"title": "No Price Created",
|
||||
"info": "This data set has no price yet. As the publisher you can create a fixed price, or a dynamic price for it. Onwards!",
|
||||
"action": {
|
||||
"name": "Create Pricing",
|
||||
"help": "Create Pricing will mint your datatokens, approve spending, and create either a pool or a fixed rate exchange in one process. You will need to approve those multiple steps in your wallet."
|
||||
}
|
||||
},
|
||||
"fixed": {
|
||||
"title": "Fixed",
|
||||
"info": "Set your price for accessing this data set. The datatoken for this data set will be worth the entered amount of OCEAN."
|
||||
},
|
||||
"dynamic": {
|
||||
"title": "Dynamic",
|
||||
"info": "Let's create a decentralized, automated market for your data set. The datatoken for this data set will be worth the entered amount of OCEAN. Additionally, you will provide liquidity into a Datatoken/OCEAN liquidity pool with Balancer.",
|
||||
"tooltips": {
|
||||
"poolInfo": "Explain what is going on here...",
|
||||
"swapFee": "Explain liquidity provider fee...",
|
||||
"communityFee": "Explain community fee...",
|
||||
"marketplaceFee": "Explain marketplace fee..."
|
||||
}
|
||||
}
|
||||
},
|
||||
"pool": {
|
||||
"tooltips": {
|
||||
"price": "Explain how this price is determined...",
|
||||
|
1642
package-lock.json
generated
1642
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
30
package.json
30
package.json
@ -22,8 +22,8 @@
|
||||
"@coingecko/cryptoformat": "^0.4.2",
|
||||
"@loadable/component": "5.13.1",
|
||||
"@oceanprotocol/art": "^3.0.0",
|
||||
"@oceanprotocol/lib": "^0.6.5",
|
||||
"@oceanprotocol/react": "^0.2.2",
|
||||
"@oceanprotocol/lib": "^0.7.1",
|
||||
"@oceanprotocol/react": "^0.3.2",
|
||||
"@oceanprotocol/typographies": "^0.1.0",
|
||||
"@sindresorhus/slugify": "^1.0.0",
|
||||
"@tippyjs/react": "^4.2.0",
|
||||
@ -40,19 +40,19 @@
|
||||
"ethereum-blockies": "github:MyEtherWallet/blockies",
|
||||
"filesize": "^6.1.0",
|
||||
"formik": "^2.2.0",
|
||||
"gatsby": "^2.24.80",
|
||||
"gatsby": "^2.24.84",
|
||||
"gatsby-image": "^2.4.21",
|
||||
"gatsby-plugin-manifest": "^2.4.35",
|
||||
"gatsby-plugin-react-helmet": "^3.3.14",
|
||||
"gatsby-plugin-remove-trailing-slashes": "^2.3.13",
|
||||
"gatsby-plugin-sharp": "^2.6.42",
|
||||
"gatsby-plugin-sharp": "^2.6.43",
|
||||
"gatsby-plugin-svgr": "^2.0.2",
|
||||
"gatsby-plugin-webpack-size": "^1.0.0",
|
||||
"gatsby-source-filesystem": "^2.3.35",
|
||||
"gatsby-source-graphql": "^2.7.6",
|
||||
"gatsby-transformer-json": "^2.4.14",
|
||||
"gatsby-transformer-remark": "^2.8.42",
|
||||
"gatsby-transformer-sharp": "^2.5.18",
|
||||
"gatsby-transformer-json": "^2.4.15",
|
||||
"gatsby-transformer-remark": "^2.8.45",
|
||||
"gatsby-transformer-sharp": "^2.5.19",
|
||||
"intersection-observer": "^0.11.0",
|
||||
"is-url-superb": "^4.0.0",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
@ -74,7 +74,7 @@
|
||||
"remove-markdown": "^0.3.0",
|
||||
"shortid": "^2.2.15",
|
||||
"slugify": "^1.4.5",
|
||||
"swr": "^0.3.5",
|
||||
"swr": "^0.3.6",
|
||||
"yup": "^0.29.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
@ -86,11 +86,11 @@
|
||||
"@svgr/webpack": "^5.4.0",
|
||||
"@testing-library/jest-dom": "^5.11.4",
|
||||
"@testing-library/react": "^11.1.0",
|
||||
"@types/jest": "^26.0.14",
|
||||
"@types/jest": "^26.0.15",
|
||||
"@types/loadable__component": "^5.13.1",
|
||||
"@types/lodash.debounce": "^4.0.3",
|
||||
"@types/lodash.omit": "^4.5.6",
|
||||
"@types/node": "^14.11.10",
|
||||
"@types/node": "^14.14.0",
|
||||
"@types/react": "^16.9.53",
|
||||
"@types/react-datepicker": "^3.1.1",
|
||||
"@types/react-helmet": "^6.1.0",
|
||||
@ -99,18 +99,18 @@
|
||||
"@types/remove-markdown": "^0.1.1",
|
||||
"@types/shortid": "0.0.29",
|
||||
"@types/yup": "^0.29.8",
|
||||
"@typescript-eslint/eslint-plugin": "^4.4.1",
|
||||
"@typescript-eslint/parser": "^4.4.1",
|
||||
"@typescript-eslint/eslint-plugin": "^4.5.0",
|
||||
"@typescript-eslint/parser": "^4.5.0",
|
||||
"babel-loader": "^8.1.0",
|
||||
"babel-preset-react-app": "^9.1.2",
|
||||
"eslint": "^7.11.0",
|
||||
"eslint-config-oceanprotocol": "^1.5.0",
|
||||
"eslint-config-prettier": "^6.13.0",
|
||||
"eslint-plugin-prettier": "^3.1.4",
|
||||
"eslint-plugin-react": "^7.21.4",
|
||||
"eslint-plugin-react-hooks": "^4.1.2",
|
||||
"eslint-plugin-react": "^7.21.5",
|
||||
"eslint-plugin-react-hooks": "^4.2.0",
|
||||
"identity-obj-proxy": "^3.0.0",
|
||||
"jest": "^26.5.3",
|
||||
"jest": "^26.6.0",
|
||||
"prettier": "^2.1.2",
|
||||
"serve": "^11.3.2",
|
||||
"source-map-explorer": "^2.5.0",
|
||||
|
7
src/@types/MetaData.d.ts
vendored
7
src/@types/MetaData.d.ts
vendored
@ -4,12 +4,11 @@ import {
|
||||
AdditionalInformation,
|
||||
ServiceMetadata
|
||||
} from '@oceanprotocol/lib'
|
||||
import { PriceOptions, DataTokenOptions } from '@oceanprotocol/react'
|
||||
import { DataTokenOptions, PriceOptions } from '@oceanprotocol/react'
|
||||
|
||||
export interface AdditionalInformationMarket extends AdditionalInformation {
|
||||
links?: File[]
|
||||
termsAndConditions: boolean
|
||||
priceType: string
|
||||
}
|
||||
|
||||
export interface MetadataMarket extends Metadata {
|
||||
@ -22,8 +21,6 @@ export interface MetadataMarket extends Metadata {
|
||||
export interface PriceOptionsMarket extends PriceOptions {
|
||||
// easier to keep this as number for Yup input validation
|
||||
swapFee: number
|
||||
// collect datatoken info for publishing
|
||||
datatoken?: DataTokenOptions
|
||||
}
|
||||
|
||||
export interface MetadataPublishForm {
|
||||
@ -33,7 +30,7 @@ export interface MetadataPublishForm {
|
||||
files: string | File[]
|
||||
author: string
|
||||
license: string
|
||||
price: PriceOptionsMarket
|
||||
dataTokenOptions: DataTokenOptions
|
||||
access: 'Download' | 'Compute' | string
|
||||
termsAndConditions: boolean
|
||||
// ---- optional fields ----
|
||||
|
@ -30,6 +30,10 @@
|
||||
font-size: var(--font-size-small);
|
||||
}
|
||||
|
||||
.action {
|
||||
margin-top: calc(var(--spacer) / 2);
|
||||
}
|
||||
|
||||
/* States */
|
||||
.error {
|
||||
border-color: var(--rbrand-alert-ed);
|
||||
@ -43,7 +47,7 @@
|
||||
|
||||
.info {
|
||||
border-color: var(--brand-alert-yellow);
|
||||
color: var(--brand-alert-yellow);
|
||||
color: #9f7e19;
|
||||
}
|
||||
|
||||
.warning {
|
||||
|
@ -1,19 +1,35 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
import React, { ReactElement, FormEvent } from 'react'
|
||||
import styles from './Alert.module.css'
|
||||
import Button from './Button'
|
||||
|
||||
export default function Alert({
|
||||
title,
|
||||
text,
|
||||
state
|
||||
state,
|
||||
action
|
||||
}: {
|
||||
title?: string
|
||||
text: string
|
||||
state: 'error' | 'warning' | 'info' | 'success'
|
||||
action?: {
|
||||
name: string
|
||||
handleAction: (e: FormEvent<HTMLButtonElement>) => void
|
||||
}
|
||||
}): ReactElement {
|
||||
return (
|
||||
<div className={`${styles.alert} ${styles[state]}`}>
|
||||
{title && <h3 className={styles.title}>{title}</h3>}
|
||||
<p className={styles.text}>{text}</p>
|
||||
{action && (
|
||||
<Button
|
||||
className={styles.action}
|
||||
size="small"
|
||||
style="primary"
|
||||
onClick={action.handleAction}
|
||||
>
|
||||
{action.name}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -242,7 +242,7 @@ input[type='range']::-moz-range-track {
|
||||
|
||||
.small {
|
||||
font-size: var(--font-size-small);
|
||||
min-height: 34px;
|
||||
height: 34px;
|
||||
padding: calc(var(--spacer) / 4);
|
||||
}
|
||||
|
||||
@ -252,7 +252,7 @@ input[type='range']::-moz-range-track {
|
||||
|
||||
.prefix.small,
|
||||
.postfix.small {
|
||||
min-height: 34px;
|
||||
height: 34px;
|
||||
font-size: var(--font-size-mini);
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,7 @@ import styles from './InputElement.module.css'
|
||||
import { InputProps } from '.'
|
||||
import FilesInput from '../../molecules/FormFields/FilesInput'
|
||||
import Terms from '../../molecules/FormFields/Terms'
|
||||
import Price from '../../molecules/FormFields/Price'
|
||||
import Datatoken from '../../molecules/FormFields/Datatoken'
|
||||
|
||||
const DefaultInput = ({
|
||||
small,
|
||||
@ -87,8 +87,8 @@ export default function InputElement({
|
||||
)
|
||||
case 'files':
|
||||
return <FilesInput name={name} {...field} {...props} />
|
||||
case 'price':
|
||||
return <Price name={name} {...field} {...props} />
|
||||
case 'datatoken':
|
||||
return <Datatoken name={name} {...field} {...props} />
|
||||
case 'terms':
|
||||
return <Terms name={name} options={options} {...field} {...props} />
|
||||
default:
|
||||
|
@ -1,12 +1,12 @@
|
||||
.loaderWrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.loader {
|
||||
display: block;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
flex: 0 0 1.2rem;
|
||||
width: 1.2rem;
|
||||
height: 1.2rem;
|
||||
border-radius: 50%;
|
||||
border: 2px solid var(--brand-purple);
|
||||
border-top-color: var(--brand-violet);
|
||||
|
@ -28,7 +28,7 @@ export default function SuccessConfetti({
|
||||
action
|
||||
}: {
|
||||
success: string
|
||||
action: ReactNode
|
||||
action?: ReactNode
|
||||
}): ReactElement {
|
||||
// Have some confetti upon success
|
||||
useEffect(() => {
|
||||
|
@ -0,0 +1,5 @@
|
||||
.datatoken {
|
||||
border: 1px solid var(--brand-grey-lighter);
|
||||
padding: calc(var(--spacer) / 3);
|
||||
border-radius: var(--border-radius);
|
||||
}
|
32
src/components/molecules/FormFields/Datatoken/index.tsx
Normal file
32
src/components/molecules/FormFields/Datatoken/index.tsx
Normal file
@ -0,0 +1,32 @@
|
||||
import { useField } from 'formik'
|
||||
import { InputProps } from '../../../atoms/Input'
|
||||
import { useOcean } from '@oceanprotocol/react'
|
||||
import React, { ReactElement, useEffect } from 'react'
|
||||
import styles from './index.module.css'
|
||||
|
||||
import RefreshName from './RefreshName'
|
||||
|
||||
export default function Datatoken(props: InputProps): ReactElement {
|
||||
const { ocean } = useOcean()
|
||||
const [field, meta, helpers] = useField(props.name)
|
||||
|
||||
function generateName() {
|
||||
if (!ocean) return
|
||||
const dataTokenOptions = ocean.datatokens.generateDtName()
|
||||
helpers.setValue({ ...dataTokenOptions })
|
||||
}
|
||||
|
||||
// Generate new DT name & symbol
|
||||
useEffect(() => {
|
||||
if (!ocean) return
|
||||
generateName()
|
||||
}, [ocean])
|
||||
|
||||
return (
|
||||
<div className={styles.datatoken}>
|
||||
<strong>{field?.value?.name}</strong> —{' '}
|
||||
<strong>{field?.value?.symbol}</strong>
|
||||
<RefreshName generateName={generateName} />
|
||||
</div>
|
||||
)
|
||||
}
|
@ -1,116 +0,0 @@
|
||||
import React, { ReactElement, useState, useEffect } from 'react'
|
||||
import { graphql, useStaticQuery } from 'gatsby'
|
||||
import { InputProps } from '../../../atoms/Input'
|
||||
import styles from './index.module.css'
|
||||
import Tabs from '../../../atoms/Tabs'
|
||||
import Fixed from './Fixed'
|
||||
import Dynamic from './Dynamic'
|
||||
import { useField, useFormikContext } from 'formik'
|
||||
import { useUserPreferences } from '../../../../providers/UserPreferences'
|
||||
import { useOcean } from '@oceanprotocol/react'
|
||||
import { PriceOptionsMarket } from '../../../../@types/MetaData'
|
||||
|
||||
const query = graphql`
|
||||
query PriceFieldQuery {
|
||||
content: allFile(filter: { relativePath: { eq: "pages/publish.json" } }) {
|
||||
edges {
|
||||
node {
|
||||
childPagesJson {
|
||||
price {
|
||||
fixed {
|
||||
title
|
||||
info
|
||||
}
|
||||
dynamic {
|
||||
title
|
||||
info
|
||||
tooltips {
|
||||
poolInfo
|
||||
swapFee
|
||||
communityFee
|
||||
marketplaceFee
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export default function Price(props: InputProps): ReactElement {
|
||||
const { debug } = useUserPreferences()
|
||||
const data = useStaticQuery(query)
|
||||
const content = data.content.edges[0].node.childPagesJson.price
|
||||
const { ocean } = useOcean()
|
||||
|
||||
const [field, meta, helpers] = useField(props.name)
|
||||
const { price }: PriceOptionsMarket = field.value
|
||||
|
||||
const [tokensToMint, setTokensToMint] = useState<number>()
|
||||
|
||||
function handleTabChange(tabName: string) {
|
||||
const type = tabName.toLowerCase()
|
||||
helpers.setValue({ ...field.value, type })
|
||||
}
|
||||
|
||||
function generateName() {
|
||||
if (!ocean) return
|
||||
const datatoken = ocean.datatokens.generateDtName()
|
||||
helpers.setValue({ ...field.value, datatoken })
|
||||
}
|
||||
|
||||
// Always update everything when amountOcean changes
|
||||
useEffect(() => {
|
||||
const tokensToMint = Number(price) * Number(field.value.weightOnDataToken)
|
||||
setTokensToMint(tokensToMint)
|
||||
helpers.setValue({ ...field.value, tokensToMint })
|
||||
}, [price])
|
||||
|
||||
// Generate new DT name & symbol, but only once automatically
|
||||
useEffect(() => {
|
||||
if (!ocean || typeof field?.value?.datatoken?.name !== 'undefined') return
|
||||
generateName()
|
||||
}, [ocean])
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
title: content.fixed.title,
|
||||
content: (
|
||||
<Fixed
|
||||
datatokenOptions={field.value.datatoken}
|
||||
generateName={generateName}
|
||||
content={content.fixed}
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: content.dynamic.title,
|
||||
content: (
|
||||
<Dynamic
|
||||
ocean={price}
|
||||
priceOptions={{ ...field.value, tokensToMint }}
|
||||
datatokenOptions={field.value.datatoken}
|
||||
generateName={generateName}
|
||||
content={content.dynamic}
|
||||
/>
|
||||
)
|
||||
}
|
||||
]
|
||||
|
||||
return (
|
||||
<div className={styles.price}>
|
||||
<Tabs
|
||||
items={tabs}
|
||||
handleTabChange={handleTabChange}
|
||||
defaultIndex={field?.value?.type === 'fixed' ? 0 : 1}
|
||||
/>
|
||||
{debug === true && (
|
||||
<pre>
|
||||
<code>{JSON.stringify(field.value, null, 2)}</code>
|
||||
</pre>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
@ -22,3 +22,7 @@
|
||||
.feedback {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.hasTokens {
|
||||
composes: hasTokens from './index.module.css';
|
||||
}
|
||||
|
@ -13,5 +13,5 @@ export default {
|
||||
}
|
||||
|
||||
export const Default = (): ReactElement => (
|
||||
<Compute ddo={ddo as DDO} isBalanceSufficient />
|
||||
<Compute ddo={ddo as DDO} dtBalance="1" isBalanceSufficient />
|
||||
)
|
||||
|
@ -9,7 +9,8 @@ import {
|
||||
computeOptions,
|
||||
useCompute,
|
||||
readFileContent,
|
||||
useOcean
|
||||
useOcean,
|
||||
usePricing
|
||||
} from '@oceanprotocol/react'
|
||||
import styles from './Compute.module.css'
|
||||
import Button from '../../atoms/Button'
|
||||
@ -19,14 +20,19 @@ import { useSiteMetadata } from '../../../hooks/useSiteMetadata'
|
||||
|
||||
export default function Compute({
|
||||
ddo,
|
||||
isBalanceSufficient
|
||||
isBalanceSufficient,
|
||||
dtBalance
|
||||
}: {
|
||||
ddo: DDO
|
||||
isBalanceSufficient: boolean
|
||||
dtBalance: string
|
||||
}): ReactElement {
|
||||
const { marketFeeAddress } = useSiteMetadata()
|
||||
|
||||
const { ocean } = useOcean()
|
||||
const { compute, isLoading, computeStepText, computeError } = useCompute()
|
||||
const { marketFeeAddress } = useSiteMetadata()
|
||||
const { buyDT, dtSymbol } = usePricing(ddo)
|
||||
|
||||
const computeService = ddo.findServiceByType('compute')
|
||||
const metadataService = ddo.findServiceByType('metadata')
|
||||
|
||||
@ -48,6 +54,7 @@ export default function Compute({
|
||||
computeType === '' ||
|
||||
!ocean ||
|
||||
!isBalanceSufficient
|
||||
const hasDatatoken = Number(dtBalance) >= 1
|
||||
|
||||
const onDrop = async (files: File[]) => {
|
||||
setFile(files[0])
|
||||
@ -72,6 +79,8 @@ export default function Compute({
|
||||
setIsPublished(false)
|
||||
setError('')
|
||||
|
||||
!hasDatatoken && (await buyDT('1'))
|
||||
|
||||
await compute(
|
||||
ddo.id,
|
||||
computeService,
|
||||
@ -99,6 +108,12 @@ export default function Compute({
|
||||
</div>
|
||||
<div className={styles.pricewrapper}>
|
||||
<Price ddo={ddo} conversion />
|
||||
{hasDatatoken && (
|
||||
<div className={styles.hasTokens}>
|
||||
You own {dtBalance} {dtSymbol} allowing you to use this data set
|
||||
without paying again.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -120,7 +135,7 @@ export default function Compute({
|
||||
onClick={() => startJob()}
|
||||
disabled={isComputeButtonDisabled}
|
||||
>
|
||||
Start job
|
||||
{hasDatatoken ? 'Start job' : 'Buy'}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
|
@ -12,10 +12,11 @@
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.pricewrapper button {
|
||||
margin-top: var(--spacer);
|
||||
.actions {
|
||||
width: 100%;
|
||||
margin-top: calc(var(--spacer) / 2);
|
||||
}
|
||||
|
||||
.feedback {
|
||||
width: 100%;
|
||||
.hasTokens {
|
||||
composes: hasTokens from './index.module.css';
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ export default {
|
||||
export const PricedAsset = (): ReactElement => (
|
||||
<Consume
|
||||
ddo={ddo as DDO}
|
||||
dtBalance="1"
|
||||
isBalanceSufficient
|
||||
file={new DDO(ddo).findServiceByType('metadata').attributes.main.files[0]}
|
||||
/>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
import React, { ReactElement, useEffect } from 'react'
|
||||
import { toast } from 'react-toastify'
|
||||
import { File as FileMetadata, DDO } from '@oceanprotocol/lib'
|
||||
import Button from '../../atoms/Button'
|
||||
@ -7,41 +7,56 @@ import Price from '../../atoms/Price'
|
||||
import Web3Feedback from '../../molecules/Wallet/Feedback'
|
||||
import styles from './Consume.module.css'
|
||||
import Loader from '../../atoms/Loader'
|
||||
import { useOcean, useConsume } from '@oceanprotocol/react'
|
||||
import { useOcean, useConsume, usePricing } from '@oceanprotocol/react'
|
||||
import { useSiteMetadata } from '../../../hooks/useSiteMetadata'
|
||||
|
||||
export default function Consume({
|
||||
ddo,
|
||||
file,
|
||||
isBalanceSufficient
|
||||
isBalanceSufficient,
|
||||
dtBalance
|
||||
}: {
|
||||
ddo: DDO
|
||||
file: FileMetadata
|
||||
isBalanceSufficient: boolean
|
||||
dtBalance: string
|
||||
}): ReactElement {
|
||||
const { ocean } = useOcean()
|
||||
const { marketFeeAddress } = useSiteMetadata()
|
||||
const {
|
||||
dtSymbol,
|
||||
buyDT,
|
||||
pricingStepText,
|
||||
pricingError,
|
||||
pricingIsLoading
|
||||
} = usePricing(ddo)
|
||||
const { consumeStepText, consume, consumeError } = useConsume()
|
||||
|
||||
const isDisabled = !ocean || !isBalanceSufficient
|
||||
const isDisabled =
|
||||
!ocean ||
|
||||
!isBalanceSufficient ||
|
||||
typeof consumeStepText !== 'undefined' ||
|
||||
pricingIsLoading
|
||||
const hasDatatoken = Number(dtBalance) >= 1
|
||||
|
||||
if (consumeError) {
|
||||
toast.error(consumeError)
|
||||
async function handleConsume() {
|
||||
!hasDatatoken && (await buyDT('1'))
|
||||
await consume(ddo.id, ddo.dataToken, 'access', marketFeeAddress)
|
||||
}
|
||||
|
||||
// Output errors in UI
|
||||
useEffect(() => {
|
||||
consumeError && toast.error(consumeError)
|
||||
pricingError && toast.error(pricingError)
|
||||
}, [consumeError, pricingError])
|
||||
|
||||
const PurchaseButton = () => (
|
||||
<div>
|
||||
{consumeStepText ? (
|
||||
<Loader message={consumeStepText} />
|
||||
<div className={styles.actions}>
|
||||
{consumeStepText || pricingIsLoading ? (
|
||||
<Loader message={consumeStepText || pricingStepText} />
|
||||
) : (
|
||||
<Button
|
||||
style="primary"
|
||||
onClick={() =>
|
||||
consume(ddo.id, ddo.dataToken, 'access', marketFeeAddress)
|
||||
}
|
||||
disabled={isDisabled}
|
||||
>
|
||||
Buy
|
||||
<Button style="primary" onClick={handleConsume} disabled={isDisabled}>
|
||||
{hasDatatoken ? 'Download' : 'Buy'}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
@ -55,6 +70,12 @@ export default function Consume({
|
||||
</div>
|
||||
<div className={styles.pricewrapper}>
|
||||
<Price ddo={ddo} conversion />
|
||||
{hasDatatoken && (
|
||||
<div className={styles.hasTokens}>
|
||||
You own {dtBalance} {dtSymbol} allowing you to use this data set
|
||||
without paying again.
|
||||
</div>
|
||||
)}
|
||||
<PurchaseButton />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React, { ReactElement, useEffect, useState } from 'react'
|
||||
import { useOcean, useMetadata } from '@oceanprotocol/react'
|
||||
import { useOcean, useMetadata, usePricing } from '@oceanprotocol/react'
|
||||
import { DDO, Logger } from '@oceanprotocol/lib'
|
||||
import styles from './index.module.css'
|
||||
import stylesActions from './Actions.module.css'
|
||||
@ -45,10 +45,10 @@ export default function Pool({ ddo }: { ddo: DDO }): ReactElement {
|
||||
|
||||
const { ocean, accountId } = useOcean()
|
||||
const { price } = useMetadata(ddo)
|
||||
const { dtSymbol } = usePricing(ddo)
|
||||
|
||||
const [poolTokens, setPoolTokens] = useState<string>()
|
||||
const [totalPoolTokens, setTotalPoolTokens] = useState<string>()
|
||||
const [dtSymbol, setDtSymbol] = useState<string>()
|
||||
const [userLiquidity, setUserLiquidity] = useState<Balance>()
|
||||
const [swapFee, setSwapFee] = useState<string>()
|
||||
|
||||
@ -79,12 +79,6 @@ export default function Pool({ ddo }: { ddo: DDO }): ReactElement {
|
||||
setIsLoading(true)
|
||||
|
||||
try {
|
||||
//
|
||||
// Get data token symbol
|
||||
//
|
||||
const dtSymbol = await ocean.datatokens.getSymbol(ddo.dataToken)
|
||||
setDtSymbol(dtSymbol)
|
||||
|
||||
//
|
||||
// Get everything which is in the pool
|
||||
//
|
||||
|
@ -4,3 +4,9 @@
|
||||
margin: auto;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.hasTokens {
|
||||
font-size: var(--font-size-mini);
|
||||
color: var(--color-secondary);
|
||||
margin-top: calc(var(--spacer) / 12);
|
||||
}
|
||||
|
@ -2,37 +2,63 @@ import React, { ReactElement, useState, useEffect } from 'react'
|
||||
import styles from './index.module.css'
|
||||
import Compute from './Compute'
|
||||
import Consume from './Consume'
|
||||
import { DDO } from '@oceanprotocol/lib'
|
||||
import { DDO, Logger } from '@oceanprotocol/lib'
|
||||
import Tabs from '../../atoms/Tabs'
|
||||
import { useOcean, useMetadata } from '@oceanprotocol/react'
|
||||
import compareAsBN from '../../../utils/compareAsBN'
|
||||
import Pool from './Pool'
|
||||
import { AdditionalInformationMarket } from '../../../@types/MetaData'
|
||||
|
||||
export default function AssetActions({ ddo }: { ddo: DDO }): ReactElement {
|
||||
const { balance } = useOcean()
|
||||
const { ocean, balance, accountId } = useOcean()
|
||||
const { price } = useMetadata(ddo)
|
||||
const [isBalanceSufficient, setIsBalanceSufficient] = useState<boolean>()
|
||||
const [dtBalance, setDtBalance] = useState<string>()
|
||||
|
||||
const isCompute = Boolean(ddo.findServiceByType('compute'))
|
||||
const { attributes } = ddo.findServiceByType('metadata')
|
||||
|
||||
// Get and set user DT balance
|
||||
useEffect(() => {
|
||||
if (!ocean || !accountId) return
|
||||
|
||||
async function init() {
|
||||
try {
|
||||
const dtBalance = await ocean.datatokens.balance(
|
||||
ddo.dataToken,
|
||||
accountId
|
||||
)
|
||||
setDtBalance(dtBalance)
|
||||
} catch (e) {
|
||||
Logger.error(e.message)
|
||||
}
|
||||
}
|
||||
init()
|
||||
}, [ocean, accountId, ddo.dataToken])
|
||||
|
||||
// Check user balance against price
|
||||
useEffect(() => {
|
||||
if (!price || !price.value || !balance || !balance.ocean) return
|
||||
if (!price || !price.value || !balance || !balance.ocean || !dtBalance)
|
||||
return
|
||||
|
||||
setIsBalanceSufficient(compareAsBN(balance.ocean, `${price.value}`))
|
||||
setIsBalanceSufficient(
|
||||
compareAsBN(balance.ocean, `${price.value}`) || Number(dtBalance) >= 1
|
||||
)
|
||||
|
||||
return () => {
|
||||
setIsBalanceSufficient(false)
|
||||
}
|
||||
}, [balance, price])
|
||||
}, [balance, price, dtBalance])
|
||||
|
||||
const UseContent = isCompute ? (
|
||||
<Compute ddo={ddo} isBalanceSufficient={isBalanceSufficient} />
|
||||
<Compute
|
||||
ddo={ddo}
|
||||
dtBalance={dtBalance}
|
||||
isBalanceSufficient={isBalanceSufficient}
|
||||
/>
|
||||
) : (
|
||||
<Consume
|
||||
ddo={ddo}
|
||||
dtBalance={dtBalance}
|
||||
isBalanceSufficient={isBalanceSufficient}
|
||||
file={attributes.main.files[0]}
|
||||
/>
|
||||
@ -46,10 +72,7 @@ export default function AssetActions({ ddo }: { ddo: DDO }): ReactElement {
|
||||
]
|
||||
|
||||
// Check from metadata, cause that is available earlier
|
||||
const hasPool =
|
||||
((attributes.additionalInformation as unknown) as AdditionalInformationMarket)
|
||||
?.priceType === 'dynamic'
|
||||
// price?.type === 'pool'
|
||||
const hasPool = ddo.price?.type === 'pool'
|
||||
|
||||
hasPool &&
|
||||
tabs.push({
|
||||
|
@ -1,12 +1,11 @@
|
||||
import React, { ReactElement, useEffect, useState } from 'react'
|
||||
import React, { ReactElement } from 'react'
|
||||
import Time from '../../atoms/Time'
|
||||
import MetaItem from './MetaItem'
|
||||
import styles from './MetaFull.module.css'
|
||||
import { MetadataMarket } from '../../../@types/MetaData'
|
||||
import { DDO } from '@oceanprotocol/lib'
|
||||
import EtherscanLink from '../../atoms/EtherscanLink'
|
||||
|
||||
import { useOcean } from '@oceanprotocol/react'
|
||||
import { usePricing } from '@oceanprotocol/react'
|
||||
|
||||
export default function MetaFull({
|
||||
ddo,
|
||||
@ -15,24 +14,9 @@ export default function MetaFull({
|
||||
ddo: DDO
|
||||
metadata: MetadataMarket
|
||||
}): ReactElement {
|
||||
const { ocean } = useOcean()
|
||||
const { id, dataToken } = ddo
|
||||
const { dateCreated, datePublished, author, license } = metadata.main
|
||||
|
||||
const [dtName, setDtName] = useState<string>()
|
||||
const [dtSymbol, setDtSymbol] = useState<string>()
|
||||
|
||||
useEffect(() => {
|
||||
if (!ocean) return
|
||||
|
||||
async function getDataTokenInfo() {
|
||||
const name = await ocean.datatokens.getName(dataToken)
|
||||
setDtName(name)
|
||||
const symbol = await ocean.datatokens.getSymbol(dataToken)
|
||||
setDtSymbol(symbol)
|
||||
}
|
||||
getDataTokenInfo()
|
||||
}, [ocean, dataToken])
|
||||
const { dtSymbol, dtName } = usePricing(ddo)
|
||||
|
||||
return (
|
||||
<div className={styles.metaFull}>
|
||||
|
@ -0,0 +1,8 @@
|
||||
.feedback {
|
||||
width: 100%;
|
||||
min-height: 20vh;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
34
src/components/organisms/AssetContent/Pricing/Feedback.tsx
Normal file
34
src/components/organisms/AssetContent/Pricing/Feedback.tsx
Normal file
@ -0,0 +1,34 @@
|
||||
import Loader from '../../../atoms/Loader'
|
||||
import SuccessConfetti from '../../../atoms/SuccessConfetti'
|
||||
import React, { ReactElement } from 'react'
|
||||
import styles from './Feedback.module.css'
|
||||
import Button from '../../../atoms/Button'
|
||||
|
||||
export default function Feedback({
|
||||
success,
|
||||
pricingStepText
|
||||
}: {
|
||||
success: string
|
||||
pricingStepText: string
|
||||
}): ReactElement {
|
||||
const SuccessAction = () => (
|
||||
<Button
|
||||
style="primary"
|
||||
size="small"
|
||||
className={styles.action}
|
||||
onClick={() => window?.location.reload()}
|
||||
>
|
||||
Reload Page
|
||||
</Button>
|
||||
)
|
||||
|
||||
return (
|
||||
<div className={styles.feedback}>
|
||||
{success ? (
|
||||
<SuccessConfetti success={success} action={<SuccessAction />} />
|
||||
) : (
|
||||
<Loader message={pricingStepText} />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
@ -8,7 +8,7 @@
|
||||
}
|
||||
|
||||
.icon {
|
||||
composes: box from '../../../atoms/Box.module.css';
|
||||
composes: box from '../../../../atoms/Box.module.css';
|
||||
padding: calc(var(--spacer) / 1.5);
|
||||
width: 6rem;
|
||||
height: 6rem;
|
@ -1,11 +1,10 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
import stylesIndex from './index.module.css'
|
||||
import styles from './Coin.module.css'
|
||||
import InputElement from '../../../atoms/Input/InputElement'
|
||||
import { ReactComponent as Logo } from '../../../../images/logo.svg'
|
||||
import Conversion from '../../../atoms/Price/Conversion'
|
||||
import InputElement from '../../../../atoms/Input/InputElement'
|
||||
import { ReactComponent as Logo } from '../../../../../images/logo.svg'
|
||||
import Conversion from '../../../../atoms/Price/Conversion'
|
||||
import { DataTokenOptions } from '@oceanprotocol/react'
|
||||
import RefreshName from './RefreshName'
|
||||
import { useField } from 'formik'
|
||||
import Error from './Error'
|
||||
|
||||
@ -13,13 +12,11 @@ export default function Coin({
|
||||
datatokenOptions,
|
||||
name,
|
||||
weight,
|
||||
generateName,
|
||||
readOnly
|
||||
}: {
|
||||
datatokenOptions: DataTokenOptions
|
||||
name: string
|
||||
weight: string
|
||||
generateName?: () => void
|
||||
readOnly?: boolean
|
||||
}): ReactElement {
|
||||
const [field, meta] = useField(name)
|
||||
@ -32,9 +29,6 @@ export default function Coin({
|
||||
|
||||
<h4 className={styles.tokenName}>
|
||||
{datatokenOptions?.name || 'Data Token'}
|
||||
{datatokenOptions?.name && typeof generateName === 'function' && (
|
||||
<RefreshName generateName={generateName} />
|
||||
)}
|
||||
</h4>
|
||||
|
||||
<div className={styles.weight}>
|
||||
@ -47,6 +41,8 @@ export default function Coin({
|
||||
readOnly={readOnly}
|
||||
prefix={datatokenOptions?.symbol || 'DT'}
|
||||
min="1"
|
||||
name={name}
|
||||
value={field.value}
|
||||
{...field}
|
||||
/>
|
||||
{datatokenOptions?.symbol === 'OCEAN' && (
|
@ -40,6 +40,7 @@
|
||||
margin-left: -2rem;
|
||||
margin-right: -2rem;
|
||||
border-bottom: 1px solid var(--brand-grey-lighter);
|
||||
background: var(--brand-grey-dimmed);
|
||||
}
|
||||
|
||||
@media screen and (min-width: 40rem) {
|
@ -1,34 +1,34 @@
|
||||
import { DataTokenOptions, useOcean } from '@oceanprotocol/react'
|
||||
import PriceUnit from '../../../atoms/Price/PriceUnit'
|
||||
import { useOcean, usePricing } from '@oceanprotocol/react'
|
||||
import PriceUnit from '../../../../atoms/Price/PriceUnit'
|
||||
import React, { ReactElement, useEffect, useState } from 'react'
|
||||
import { PriceOptionsMarket } from '../../../../@types/MetaData'
|
||||
import { useSiteMetadata } from '../../../../hooks/useSiteMetadata'
|
||||
import { isCorrectNetwork } from '../../../../utils/wallet'
|
||||
import Alert from '../../../atoms/Alert'
|
||||
import FormHelp from '../../../atoms/Input/Help'
|
||||
import Tooltip from '../../../atoms/Tooltip'
|
||||
import Wallet from '../../Wallet'
|
||||
import { useSiteMetadata } from '../../../../../hooks/useSiteMetadata'
|
||||
import { isCorrectNetwork } from '../../../../../utils/wallet'
|
||||
import Alert from '../../../../atoms/Alert'
|
||||
import FormHelp from '../../../../atoms/Input/Help'
|
||||
import Tooltip from '../../../../atoms/Tooltip'
|
||||
import Wallet from '../../../../molecules/Wallet'
|
||||
import Coin from './Coin'
|
||||
import styles from './Dynamic.module.css'
|
||||
import Fees from './Fees'
|
||||
import stylesIndex from './index.module.css'
|
||||
import { useFormikContext } from 'formik'
|
||||
import { PriceOptionsMarket } from '../../../../../@types/MetaData'
|
||||
import { DDO } from '@oceanprotocol/lib'
|
||||
|
||||
export default function Dynamic({
|
||||
ocean,
|
||||
priceOptions,
|
||||
datatokenOptions,
|
||||
generateName,
|
||||
ddo,
|
||||
content
|
||||
}: {
|
||||
ocean: number
|
||||
priceOptions: PriceOptionsMarket
|
||||
datatokenOptions: DataTokenOptions
|
||||
generateName: () => void
|
||||
ddo: DDO
|
||||
content: any
|
||||
}): ReactElement {
|
||||
const { appConfig } = useSiteMetadata()
|
||||
const { account, balance, networkId, refreshBalance } = useOcean()
|
||||
const { weightOnDataToken } = priceOptions
|
||||
const { dtSymbol, dtName } = usePricing(ddo)
|
||||
|
||||
// Connect with form
|
||||
const { values } = useFormikContext()
|
||||
const { price, weightOnDataToken } = values as PriceOptionsMarket
|
||||
|
||||
const [error, setError] = useState<string>()
|
||||
const correctNetwork = isCorrectNetwork(networkId)
|
||||
@ -42,12 +42,12 @@ export default function Dynamic({
|
||||
setError(`No account connected. Please connect your Web3 wallet.`)
|
||||
} else if (!correctNetwork) {
|
||||
setError(`Wrong Network. Please connect to ${desiredNetworkName}.`)
|
||||
} else if (Number(balance.ocean) < Number(ocean)) {
|
||||
setError(`Insufficient balance. You need at least ${ocean} OCEAN`)
|
||||
} else if (Number(balance.ocean) < Number(price)) {
|
||||
setError(`Insufficient balance. You need at least ${price} OCEAN`)
|
||||
} else {
|
||||
setError(undefined)
|
||||
}
|
||||
}, [ocean, networkId, account, balance, correctNetwork, desiredNetworkName])
|
||||
}, [price, networkId, account, balance, correctNetwork, desiredNetworkName])
|
||||
|
||||
// refetch balance periodically
|
||||
useEffect(() => {
|
||||
@ -59,7 +59,7 @@ export default function Dynamic({
|
||||
return () => {
|
||||
clearInterval(balanceInterval)
|
||||
}
|
||||
}, [ocean, networkId, account])
|
||||
}, [networkId, account])
|
||||
|
||||
return (
|
||||
<div className={styles.dynamic}>
|
||||
@ -83,15 +83,14 @@ export default function Dynamic({
|
||||
|
||||
<div className={styles.tokens}>
|
||||
<Coin
|
||||
name="price.price"
|
||||
name="price"
|
||||
datatokenOptions={{ symbol: 'OCEAN', name: 'Ocean Token' }}
|
||||
weight={`${100 - Number(Number(weightOnDataToken) * 10)}%`}
|
||||
/>
|
||||
<Coin
|
||||
name="price.tokensToMint"
|
||||
datatokenOptions={datatokenOptions}
|
||||
name="dtAmount"
|
||||
datatokenOptions={{ symbol: dtSymbol, name: dtName }}
|
||||
weight={`${Number(weightOnDataToken) * 10}%`}
|
||||
generateName={generateName}
|
||||
readOnly
|
||||
/>
|
||||
</div>
|
@ -1,6 +1,6 @@
|
||||
import { FieldMetaProps } from 'formik'
|
||||
import React, { ReactElement } from 'react'
|
||||
import stylesInput from '../../../atoms/Input/index.module.css'
|
||||
import stylesInput from '../../../../atoms/Input/index.module.css'
|
||||
|
||||
export default function Error({
|
||||
meta
|
@ -1,16 +1,16 @@
|
||||
.fees {
|
||||
display: grid;
|
||||
gap: var(--spacer);
|
||||
grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
|
||||
grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
|
||||
margin-left: -2rem;
|
||||
margin-right: -2rem;
|
||||
border-bottom: 1px solid var(--brand-grey-lighter);
|
||||
margin-top: var(--spacer);
|
||||
padding: 0 var(--spacer) calc(var(--spacer) / 2) var(--spacer);
|
||||
padding: var(--spacer) var(--spacer) calc(var(--spacer) / 2) var(--spacer);
|
||||
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
border-bottom: 1px solid var(--brand-grey-lighter);
|
||||
background: var(--brand-grey-dimmed);
|
||||
}
|
||||
|
||||
.fees label {
|
@ -1,18 +1,40 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
import Tooltip from '../../../atoms/Tooltip'
|
||||
import Tooltip from '../../../../atoms/Tooltip'
|
||||
import styles from './Fees.module.css'
|
||||
import { useSiteMetadata } from '../../../../hooks/useSiteMetadata'
|
||||
import { useField } from 'formik'
|
||||
import Input from '../../../atoms/Input'
|
||||
import { useField, useFormikContext } from 'formik'
|
||||
import Input from '../../../../atoms/Input'
|
||||
import Error from './Error'
|
||||
|
||||
const Default = ({
|
||||
title,
|
||||
name,
|
||||
tooltip
|
||||
}: {
|
||||
title: string
|
||||
name: string
|
||||
tooltip: string
|
||||
}) => (
|
||||
<Input
|
||||
label={
|
||||
<>
|
||||
{title}
|
||||
<Tooltip content={tooltip} />
|
||||
</>
|
||||
}
|
||||
value="0.1"
|
||||
name={name}
|
||||
postfix="%"
|
||||
readOnly
|
||||
small
|
||||
/>
|
||||
)
|
||||
|
||||
export default function Fees({
|
||||
tooltips
|
||||
}: {
|
||||
tooltips: { [key: string]: string }
|
||||
}): ReactElement {
|
||||
const { appConfig } = useSiteMetadata()
|
||||
const [field, meta] = useField('price.swapFee')
|
||||
const [field, meta] = useField('swapFee')
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -34,32 +56,16 @@ export default function Fees({
|
||||
additionalComponent={<Error meta={meta} />}
|
||||
/>
|
||||
|
||||
<Input
|
||||
label={
|
||||
<>
|
||||
Community Fee
|
||||
<Tooltip content={tooltips.communityFee} />
|
||||
</>
|
||||
}
|
||||
value="0.1"
|
||||
<Default
|
||||
title="Community Fee"
|
||||
name="communityFee"
|
||||
postfix="%"
|
||||
readOnly
|
||||
small
|
||||
tooltip={tooltips.communityFee}
|
||||
/>
|
||||
|
||||
<Input
|
||||
label={
|
||||
<>
|
||||
Marketplace Fee
|
||||
<Tooltip content={tooltips.marketplaceFee} />
|
||||
</>
|
||||
}
|
||||
value="0.1"
|
||||
<Default
|
||||
title="Marketplace Fee"
|
||||
name="marketplaceFee"
|
||||
postfix="%"
|
||||
readOnly
|
||||
small
|
||||
tooltip={tooltips.marketplaceFee}
|
||||
/>
|
||||
</div>
|
||||
</>
|
@ -14,11 +14,16 @@
|
||||
}
|
||||
|
||||
.grid {
|
||||
margin-top: var(--spacer);
|
||||
margin-left: -2rem;
|
||||
margin-right: -2rem;
|
||||
padding-top: var(--spacer);
|
||||
display: grid;
|
||||
gap: var(--spacer);
|
||||
grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
|
||||
justify-content: center;
|
||||
background: var(--brand-grey-dimmed);
|
||||
border-top: 1px solid var(--brand-grey-lighter);
|
||||
border-bottom: 1px solid var(--brand-grey-lighter);
|
||||
}
|
||||
|
||||
.fixed label {
|
||||
@ -26,14 +31,14 @@
|
||||
}
|
||||
|
||||
.datatoken {
|
||||
margin-top: calc(var(--spacer) / 6);
|
||||
margin-top: calc(var(--spacer) / 2);
|
||||
color: var(--color-secondary);
|
||||
font-size: var(--font-size-small);
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
|
||||
.datatoken h4 {
|
||||
font-size: var(--font-size-small);
|
||||
font-size: var(--font-size-base);
|
||||
color: var(--color-secondary);
|
||||
margin: 0;
|
||||
}
|
@ -1,24 +1,23 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
import stylesIndex from './index.module.css'
|
||||
import styles from './Fixed.module.css'
|
||||
import FormHelp from '../../../atoms/Input/Help'
|
||||
import Conversion from '../../../atoms/Price/Conversion'
|
||||
import { DataTokenOptions } from '@oceanprotocol/react'
|
||||
import RefreshName from './RefreshName'
|
||||
import FormHelp from '../../../../atoms/Input/Help'
|
||||
import Conversion from '../../../../atoms/Price/Conversion'
|
||||
import { useField } from 'formik'
|
||||
import Input from '../../../atoms/Input'
|
||||
import Input from '../../../../atoms/Input'
|
||||
import Error from './Error'
|
||||
import { DDO } from '@oceanprotocol/lib'
|
||||
import { usePricing } from '@oceanprotocol/react'
|
||||
|
||||
export default function Fixed({
|
||||
datatokenOptions,
|
||||
generateName,
|
||||
ddo,
|
||||
content
|
||||
}: {
|
||||
datatokenOptions: DataTokenOptions
|
||||
generateName: () => void
|
||||
ddo: DDO
|
||||
content: any
|
||||
}): ReactElement {
|
||||
const [field, meta] = useField('price.price')
|
||||
const [field, meta] = useField('price')
|
||||
const { dtName, dtSymbol } = usePricing(ddo)
|
||||
|
||||
return (
|
||||
<div className={styles.fixed}>
|
||||
@ -29,7 +28,7 @@ export default function Fixed({
|
||||
<Input
|
||||
label="Ocean Token"
|
||||
value={field.value}
|
||||
name="price.price"
|
||||
name="price"
|
||||
type="number"
|
||||
prefix="OCEAN"
|
||||
min="1"
|
||||
@ -43,16 +42,11 @@ export default function Fixed({
|
||||
/>
|
||||
<Error meta={meta} />
|
||||
</div>
|
||||
|
||||
{datatokenOptions && (
|
||||
<div className={styles.datatoken}>
|
||||
<h4>
|
||||
Data Token <RefreshName generateName={generateName} />
|
||||
</h4>
|
||||
<strong>{datatokenOptions?.name}</strong> —{' '}
|
||||
<strong>{datatokenOptions?.symbol}</strong>
|
||||
</div>
|
||||
)}
|
||||
<div className={styles.datatoken}>
|
||||
<h4>
|
||||
= <strong>1</strong> {dtName} — {dtSymbol}
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
@ -1,9 +1,3 @@
|
||||
.price {
|
||||
border: 1px solid var(--brand-grey-lighter);
|
||||
background: var(--brand-grey-dimmed);
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 0;
|
||||
}
|
||||
@ -36,3 +30,18 @@
|
||||
text-align: center;
|
||||
margin-bottom: calc(var(--spacer) / 1.5);
|
||||
}
|
||||
|
||||
.actions {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.actions button {
|
||||
margin-left: calc(var(--spacer) / 2);
|
||||
margin-right: calc(var(--spacer) / 2);
|
||||
}
|
||||
|
||||
.actionsHelp {
|
||||
margin-top: calc(var(--spacer) / 2);
|
||||
padding-left: var(--spacer);
|
||||
padding-right: var(--spacer);
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
import React, { ReactElement, useEffect } from 'react'
|
||||
import styles from './index.module.css'
|
||||
import Tabs from '../../../../atoms/Tabs'
|
||||
import Fixed from './Fixed'
|
||||
import Dynamic from './Dynamic'
|
||||
import { useFormikContext } from 'formik'
|
||||
import { useUserPreferences } from '../../../../../providers/UserPreferences'
|
||||
import { PriceOptionsMarket } from '../../../../../@types/MetaData'
|
||||
import Button from '../../../../atoms/Button'
|
||||
import { DDO } from '@oceanprotocol/lib'
|
||||
import FormHelp from '../../../../atoms/Input/Help'
|
||||
|
||||
export default function FormPricing({
|
||||
ddo,
|
||||
setShowPricing,
|
||||
content
|
||||
}: {
|
||||
ddo: DDO
|
||||
setShowPricing: (value: boolean) => void
|
||||
content: any
|
||||
}): ReactElement {
|
||||
const { debug } = useUserPreferences()
|
||||
|
||||
// Connect with form
|
||||
const { values, setFieldValue, submitForm } = useFormikContext()
|
||||
const { price, weightOnDataToken, type } = values as PriceOptionsMarket
|
||||
|
||||
// Switch type value upon tab change
|
||||
function handleTabChange(tabName: string) {
|
||||
const type = tabName.toLowerCase()
|
||||
setFieldValue('type', type)
|
||||
}
|
||||
|
||||
// Always update everything when price value changes
|
||||
useEffect(() => {
|
||||
const dtAmount = Number(price) * Number(weightOnDataToken)
|
||||
setFieldValue('dtAmount', dtAmount)
|
||||
}, [price, weightOnDataToken])
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
title: content.fixed.title,
|
||||
content: <Fixed content={content.fixed} ddo={ddo} />
|
||||
},
|
||||
{
|
||||
title: content.dynamic.title,
|
||||
content: <Dynamic content={content.dynamic} ddo={ddo} />
|
||||
}
|
||||
]
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tabs
|
||||
items={tabs}
|
||||
handleTabChange={handleTabChange}
|
||||
defaultIndex={type === 'fixed' ? 0 : 1}
|
||||
/>
|
||||
|
||||
<div className={styles.actions}>
|
||||
<Button style="primary" onClick={() => submitForm()}>
|
||||
{content.empty.action.name}
|
||||
</Button>
|
||||
<Button style="text" size="small" onClick={() => setShowPricing(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
<FormHelp className={styles.actionsHelp}>
|
||||
{content.empty.action.help}
|
||||
</FormHelp>
|
||||
</div>
|
||||
|
||||
{debug === true && (
|
||||
<pre>
|
||||
<code>{JSON.stringify(values, null, 2)}</code>
|
||||
</pre>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
.pricing {
|
||||
composes: box from '../../../atoms/Box.module.css';
|
||||
padding: 0;
|
||||
padding-bottom: var(--spacer);
|
||||
margin-top: var(--spacer);
|
||||
}
|
||||
|
||||
.pricing [class*='alert'] {
|
||||
margin: var(--spacer);
|
||||
margin-bottom: 0;
|
||||
}
|
138
src/components/organisms/AssetContent/Pricing/index.tsx
Normal file
138
src/components/organisms/AssetContent/Pricing/index.tsx
Normal file
@ -0,0 +1,138 @@
|
||||
import React, { FormEvent, ReactElement, useState } from 'react'
|
||||
import { Formik } from 'formik'
|
||||
import { initialValues, validationSchema } from '../../../../models/FormPricing'
|
||||
import { DDO, Logger } from '@oceanprotocol/lib'
|
||||
import { usePricing } from '@oceanprotocol/react'
|
||||
import { PriceOptionsMarket } from '../../../../@types/MetaData'
|
||||
import Alert from '../../../atoms/Alert'
|
||||
import styles from './index.module.css'
|
||||
import FormPricing from './FormPricing'
|
||||
import { toast } from 'react-toastify'
|
||||
import Feedback from './Feedback'
|
||||
import { graphql, useStaticQuery } from 'gatsby'
|
||||
|
||||
const query = graphql`
|
||||
query PricingQuery {
|
||||
content: allFile(filter: { relativePath: { eq: "price.json" } }) {
|
||||
edges {
|
||||
node {
|
||||
childContentJson {
|
||||
create {
|
||||
empty {
|
||||
title
|
||||
info
|
||||
action {
|
||||
name
|
||||
help
|
||||
}
|
||||
}
|
||||
fixed {
|
||||
title
|
||||
info
|
||||
}
|
||||
dynamic {
|
||||
title
|
||||
info
|
||||
tooltips {
|
||||
poolInfo
|
||||
swapFee
|
||||
communityFee
|
||||
marketplaceFee
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export default function Pricing({ ddo }: { ddo: DDO }): ReactElement {
|
||||
// Get content
|
||||
const data = useStaticQuery(query)
|
||||
const content = data.content.edges[0].node.childContentJson.create
|
||||
|
||||
// View states
|
||||
const [showPricing, setShowPricing] = useState(false)
|
||||
const [success, setSuccess] = useState<string>()
|
||||
|
||||
const {
|
||||
createPricing,
|
||||
pricingIsLoading,
|
||||
pricingError,
|
||||
pricingStepText
|
||||
} = usePricing(ddo)
|
||||
|
||||
const hasFeedback = pricingIsLoading || typeof success !== 'undefined'
|
||||
|
||||
async function handleCreatePricing(values: PriceOptionsMarket) {
|
||||
try {
|
||||
const priceOptions = {
|
||||
...values,
|
||||
// swapFee is tricky: to get 0.1% you need to send 0.001 as value
|
||||
swapFee: `${values.swapFee / 100}`
|
||||
}
|
||||
|
||||
const tx = await createPricing(priceOptions)
|
||||
|
||||
// Pricing failed
|
||||
if (!tx || pricingError) {
|
||||
toast.error(pricingError || 'Price creation failed.')
|
||||
Logger.error(pricingError || 'Price creation failed.')
|
||||
return
|
||||
}
|
||||
|
||||
// Pricing succeeded
|
||||
setSuccess(
|
||||
`🎉 Successfully created a ${values.type} price. 🎉 Reload the page to get all updates.`
|
||||
)
|
||||
Logger.log(`Transaction: ${tx}`)
|
||||
} catch (error) {
|
||||
toast.error(error.message)
|
||||
Logger.error(error.message)
|
||||
}
|
||||
}
|
||||
|
||||
function handleShowPricingForm(e: FormEvent<HTMLButtonElement>) {
|
||||
e.preventDefault()
|
||||
setShowPricing(true)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.pricing}>
|
||||
<Formik
|
||||
initialValues={initialValues}
|
||||
validationSchema={validationSchema}
|
||||
onSubmit={async (values, { setSubmitting }) => {
|
||||
// move user's focus to top of screen
|
||||
window.scrollTo({ top: 0, left: 0, behavior: 'smooth' })
|
||||
|
||||
// Kick off price creation
|
||||
await handleCreatePricing(values)
|
||||
setSubmitting(false)
|
||||
}}
|
||||
>
|
||||
{hasFeedback ? (
|
||||
<Feedback success={success} pricingStepText={pricingStepText} />
|
||||
) : showPricing ? (
|
||||
<FormPricing
|
||||
ddo={ddo}
|
||||
setShowPricing={setShowPricing}
|
||||
content={content}
|
||||
/>
|
||||
) : (
|
||||
<Alert
|
||||
state="info"
|
||||
title={content.empty.title}
|
||||
text={content.empty.info}
|
||||
action={{
|
||||
name: content.empty.action.name,
|
||||
handleAction: handleShowPricingForm
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Formik>
|
||||
</div>
|
||||
)
|
||||
}
|
@ -5,6 +5,10 @@
|
||||
margin-top: -1.5rem;
|
||||
}
|
||||
|
||||
.grid > div {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.content {
|
||||
composes: box from '../../atoms/Box.module.css';
|
||||
margin-top: var(--spacer);
|
||||
@ -12,7 +16,7 @@
|
||||
|
||||
@media (min-width: 60rem) {
|
||||
.grid {
|
||||
grid-template-columns: 1.5fr minmax(0, 1fr);
|
||||
grid-template-columns: 1.5fr 1fr;
|
||||
}
|
||||
|
||||
.sticky {
|
||||
|
@ -9,6 +9,8 @@ import styles from './index.module.css'
|
||||
import AssetActions from '../AssetActions'
|
||||
import { DDO } from '@oceanprotocol/lib'
|
||||
import { useUserPreferences } from '../../../providers/UserPreferences'
|
||||
import Pricing from './Pricing'
|
||||
import { useOcean } from '@oceanprotocol/react'
|
||||
|
||||
export interface AssetContentProps {
|
||||
metadata: MetadataMarket
|
||||
@ -22,45 +24,55 @@ export default function AssetContent({
|
||||
}: AssetContentProps): ReactElement {
|
||||
const { datePublished } = metadata.main
|
||||
const { debug } = useUserPreferences()
|
||||
const { accountId } = useOcean()
|
||||
|
||||
const isOwner = accountId === ddo.publicKey[0].owner
|
||||
const hasNoPrice = ddo.price.type === ''
|
||||
const showPricing = isOwner && hasNoPrice
|
||||
|
||||
return (
|
||||
<article className={styles.grid}>
|
||||
<div className={styles.content}>
|
||||
<aside className={styles.meta}>
|
||||
<p>{datePublished && <Time date={datePublished} />}</p>
|
||||
{metadata?.additionalInformation?.categories?.length && (
|
||||
<p>
|
||||
<Link
|
||||
to={`/search?categories=["${metadata?.additionalInformation?.categories[0]}"]`}
|
||||
>
|
||||
{metadata?.additionalInformation?.categories[0]}
|
||||
</Link>
|
||||
</p>
|
||||
)}
|
||||
</aside>
|
||||
<div>
|
||||
{showPricing && <Pricing ddo={ddo} />}
|
||||
|
||||
<Markdown text={metadata?.additionalInformation?.description || ''} />
|
||||
<div className={styles.content}>
|
||||
<aside className={styles.meta}>
|
||||
<p>{datePublished && <Time date={datePublished} />}</p>
|
||||
{metadata?.additionalInformation?.categories?.length && (
|
||||
<p>
|
||||
<Link
|
||||
to={`/search?categories=["${metadata?.additionalInformation?.categories[0]}"]`}
|
||||
>
|
||||
{metadata?.additionalInformation?.categories[0]}
|
||||
</Link>
|
||||
</p>
|
||||
)}
|
||||
</aside>
|
||||
|
||||
<MetaSecondary metadata={metadata} />
|
||||
<Markdown text={metadata?.additionalInformation?.description || ''} />
|
||||
|
||||
<MetaFull ddo={ddo} metadata={metadata} />
|
||||
<MetaSecondary metadata={metadata} />
|
||||
|
||||
<div className={styles.buttonGroup}>
|
||||
{/* <EditAction
|
||||
<MetaFull ddo={ddo} metadata={metadata} />
|
||||
|
||||
<div className={styles.buttonGroup}>
|
||||
{/* <EditAction
|
||||
ddo={ddo}
|
||||
ocean={ocean}
|
||||
account={account}
|
||||
refetchMetadata={refetchMetadata}
|
||||
/> */}
|
||||
{/* <DeleteAction ddo={ddo} /> */}
|
||||
</div>
|
||||
{/* <DeleteAction ddo={ddo} /> */}
|
||||
</div>
|
||||
|
||||
{debug === true && (
|
||||
<pre>
|
||||
<code>{JSON.stringify(ddo, null, 2)}</code>
|
||||
</pre>
|
||||
)}
|
||||
{debug === true && (
|
||||
<pre>
|
||||
<code>{JSON.stringify(ddo, null, 2)}</code>
|
||||
</pre>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className={styles.sticky}>
|
||||
<AssetActions ddo={ddo} />
|
||||
|
@ -3,28 +3,43 @@ import { MetadataPublishForm } from '../../../@types/MetaData'
|
||||
import styles from './index.module.css'
|
||||
import { transformPublishFormToMetadata } from './utils'
|
||||
|
||||
const Output = ({ title, output }: { title: string; output: any }) => (
|
||||
<div>
|
||||
<h5>{title}</h5>
|
||||
<pre>
|
||||
<code>{JSON.stringify(output, null, 2)}</code>
|
||||
</pre>
|
||||
</div>
|
||||
)
|
||||
|
||||
export default function Debug({
|
||||
values
|
||||
}: {
|
||||
values: Partial<MetadataPublishForm>
|
||||
}): ReactElement {
|
||||
const ddo = {
|
||||
'@context': 'https://w3id.org/did/v1',
|
||||
dataTokenInfo: {
|
||||
...values.dataTokenOptions
|
||||
},
|
||||
service: [
|
||||
{
|
||||
index: 0,
|
||||
type: 'metadata',
|
||||
attributes: { ...transformPublishFormToMetadata(values) }
|
||||
},
|
||||
{
|
||||
index: 1,
|
||||
type: values.access,
|
||||
attributes: {}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.grid}>
|
||||
<div>
|
||||
<h5>Collected Form Values</h5>
|
||||
<pre>
|
||||
<code>{JSON.stringify(values, null, 2)}</code>
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h5>Transformed Values</h5>
|
||||
<pre>
|
||||
<code>
|
||||
{JSON.stringify(transformPublishFormToMetadata(values), null, 2)}
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
<Output title="Collected Form Values" output={values} />
|
||||
<Output title="Transformed DDO Values" output={ddo} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -4,17 +4,18 @@ import Loader from '../../atoms/Loader'
|
||||
import React, { ReactElement } from 'react'
|
||||
import styles from './Feedback.module.css'
|
||||
import SuccessConfetti from '../../atoms/SuccessConfetti'
|
||||
import { DDO } from '@oceanprotocol/lib'
|
||||
|
||||
export default function Feedback({
|
||||
error,
|
||||
success,
|
||||
did,
|
||||
ddo,
|
||||
publishStepText,
|
||||
setError
|
||||
}: {
|
||||
error: string
|
||||
success: string
|
||||
did: string
|
||||
ddo: DDO
|
||||
publishStepText: string
|
||||
setError: (error: string) => void
|
||||
}): ReactElement {
|
||||
@ -22,7 +23,7 @@ export default function Feedback({
|
||||
<Button
|
||||
style="primary"
|
||||
size="small"
|
||||
href={`/asset/${did}`}
|
||||
to={`/asset/${ddo?.id}`}
|
||||
className={styles.action}
|
||||
>
|
||||
Go to data set →
|
||||
|
@ -1,12 +1,12 @@
|
||||
import React, { ReactElement, useEffect, FormEvent } from 'react'
|
||||
import styles from './PublishForm.module.css'
|
||||
import styles from './FormPublish.module.css'
|
||||
import { useOcean } from '@oceanprotocol/react'
|
||||
import { useFormikContext, Field } from 'formik'
|
||||
import { useFormikContext, Field, Form } from 'formik'
|
||||
import Input from '../../atoms/Input'
|
||||
import Button from '../../atoms/Button'
|
||||
import { FormContent, FormFieldProps } from '../../../@types/Form'
|
||||
|
||||
export default function PublishForm({
|
||||
export default function FormPublish({
|
||||
content
|
||||
}: {
|
||||
content: FormContent
|
||||
@ -37,7 +37,7 @@ export default function PublishForm({
|
||||
}
|
||||
|
||||
return (
|
||||
<form
|
||||
<Form
|
||||
className={styles.form}
|
||||
// do we need this?
|
||||
onChange={() => status === 'empty' && setStatus(null)}
|
||||
@ -61,6 +61,6 @@ export default function PublishForm({
|
||||
</Button>
|
||||
)}
|
||||
</footer>
|
||||
</form>
|
||||
</Form>
|
||||
)
|
||||
}
|
@ -44,11 +44,3 @@
|
||||
align-items: center;
|
||||
margin-bottom: calc(var(--spacer) / 2);
|
||||
}
|
||||
|
||||
.price {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.price:only-child {
|
||||
margin-right: -100%;
|
||||
}
|
||||
|
@ -7,8 +7,6 @@ import styles from './Preview.module.css'
|
||||
import File from '../../atoms/File'
|
||||
import { MetadataPublishForm } from '../../../@types/MetaData'
|
||||
import Button from '../../atoms/Button'
|
||||
import Conversion from '../../atoms/Price/Conversion'
|
||||
import PriceUnit from '../../atoms/Price/PriceUnit'
|
||||
|
||||
export default function Preview({
|
||||
values
|
||||
@ -60,25 +58,6 @@ export default function Preview({
|
||||
small
|
||||
/>
|
||||
)}
|
||||
|
||||
{values.price && (
|
||||
<div className={styles.price}>
|
||||
<MetaItem
|
||||
title={`Price: ${values.price.type}`}
|
||||
content={
|
||||
<>
|
||||
<PriceUnit
|
||||
price="1"
|
||||
symbol={values.price.datatoken?.symbol}
|
||||
small
|
||||
/>{' '}
|
||||
= <PriceUnit price={`${values.price.price}`} small />
|
||||
<Conversion price={`${values.price.price}`} />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{typeof values.links !== 'string' && values.links?.length && (
|
||||
@ -107,7 +86,7 @@ export default function Preview({
|
||||
key.includes('files') ||
|
||||
key.includes('links') ||
|
||||
key.includes('termsAndConditions') ||
|
||||
key.includes('price') ||
|
||||
key.includes('dataTokenOptions') ||
|
||||
value === undefined ||
|
||||
value === ''
|
||||
)
|
||||
|
@ -2,7 +2,7 @@ import React, { ReactElement, useState } from 'react'
|
||||
import { Formik } from 'formik'
|
||||
import { usePublish } from '@oceanprotocol/react'
|
||||
import styles from './index.module.css'
|
||||
import PublishForm from './PublishForm'
|
||||
import FormPublish from './FormPublish'
|
||||
import Web3Feedback from '../../molecules/Wallet/Feedback'
|
||||
import { FormContent } from '../../../@types/Form'
|
||||
import { initialValues, validationSchema } from '../../../models/FormPublish'
|
||||
@ -10,7 +10,7 @@ import { transformPublishFormToMetadata } from './utils'
|
||||
import Preview from './Preview'
|
||||
import { MetadataPublishForm } from '../../../@types/MetaData'
|
||||
import { useUserPreferences } from '../../../providers/UserPreferences'
|
||||
import { Logger, Metadata } from '@oceanprotocol/lib'
|
||||
import { DDO, Logger, Metadata } from '@oceanprotocol/lib'
|
||||
import { Persist } from '../../atoms/FormikPersist'
|
||||
import Debug from './Debug'
|
||||
import Feedback from './Feedback'
|
||||
@ -27,7 +27,7 @@ export default function PublishPage({
|
||||
|
||||
const [success, setSuccess] = useState<string>()
|
||||
const [error, setError] = useState<string>()
|
||||
const [did, setDid] = useState<string>()
|
||||
const [ddo, setDdo] = useState<DDO>()
|
||||
|
||||
const hasFeedback = isLoading || error || success
|
||||
|
||||
@ -36,33 +36,35 @@ export default function PublishPage({
|
||||
resetForm: () => void
|
||||
): Promise<void> {
|
||||
const metadata = transformPublishFormToMetadata(values)
|
||||
const { price } = values
|
||||
const serviceType = values.access === 'Download' ? 'access' : 'compute'
|
||||
|
||||
try {
|
||||
Logger.log('Publish with ', price, serviceType, price.datatoken)
|
||||
Logger.log(
|
||||
'Publish with ',
|
||||
metadata,
|
||||
serviceType,
|
||||
values.dataTokenOptions
|
||||
)
|
||||
|
||||
const ddo = await publish(
|
||||
(metadata as unknown) as Metadata,
|
||||
// swapFee is tricky: to get 0.1% you need to send 0.001 as value
|
||||
{ ...price, swapFee: `${price.swapFee / 100}` },
|
||||
serviceType,
|
||||
price.datatoken
|
||||
values.dataTokenOptions
|
||||
)
|
||||
|
||||
// Publish failed
|
||||
if (publishError) {
|
||||
setError(publishError)
|
||||
Logger.error(publishError)
|
||||
if (!ddo || publishError) {
|
||||
setError(publishError || 'Publishing DDO failed.')
|
||||
Logger.error(publishError || 'Publishing DDO failed.')
|
||||
return
|
||||
}
|
||||
|
||||
// Publish succeeded
|
||||
if (ddo) {
|
||||
setDid(ddo.id)
|
||||
setSuccess('🎉 Successfully published your data set. 🎉')
|
||||
resetForm()
|
||||
}
|
||||
setDdo(ddo)
|
||||
setSuccess(
|
||||
'🎉 Successfully published. 🎉 Now create a price on your data set.'
|
||||
)
|
||||
resetForm()
|
||||
} catch (error) {
|
||||
setError(error.message)
|
||||
Logger.error(error.message)
|
||||
@ -91,12 +93,12 @@ export default function PublishPage({
|
||||
error={error}
|
||||
success={success}
|
||||
publishStepText={publishStepText}
|
||||
did={did}
|
||||
ddo={ddo}
|
||||
setError={setError}
|
||||
/>
|
||||
) : (
|
||||
<article className={styles.grid}>
|
||||
<PublishForm content={content.form} />
|
||||
<FormPublish content={content.form} />
|
||||
<aside>
|
||||
<div className={styles.sticky}>
|
||||
<Preview values={values} />
|
||||
|
@ -16,8 +16,7 @@ export function transformPublishFormToMetadata(
|
||||
tags,
|
||||
links,
|
||||
termsAndConditions,
|
||||
files,
|
||||
price
|
||||
files
|
||||
} = data
|
||||
|
||||
const metadata: MetadataMarket = {
|
||||
@ -36,8 +35,7 @@ export function transformPublishFormToMetadata(
|
||||
copyrightHolder,
|
||||
tags: tags?.split(','),
|
||||
links: typeof links !== 'string' && links,
|
||||
termsAndConditions,
|
||||
priceType: price.type
|
||||
termsAndConditions
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -23,13 +23,17 @@ export default function PageTemplateAssetDetails({
|
||||
|
||||
useEffect(() => {
|
||||
async function init() {
|
||||
if (ddo) return
|
||||
|
||||
try {
|
||||
const metadataCache = new MetadataCache(config.metadataCacheUri, Logger)
|
||||
const ddo = await metadataCache.retrieveDDO(did)
|
||||
|
||||
if (!ddo) {
|
||||
setTitle('Could not retrieve asset')
|
||||
setError('The DDO was not found in MetadataCache.')
|
||||
setError(
|
||||
`The DDO for ${did} was not found in MetadataCache. If you just published a new data set, wait some seconds and refresh this page.`
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
@ -44,7 +48,11 @@ export default function PageTemplateAssetDetails({
|
||||
}
|
||||
}
|
||||
init()
|
||||
}, [did, config.metadataCacheUri])
|
||||
|
||||
// Periodically try to get DDO when not present yet
|
||||
const timer = !ddo && setInterval(() => init(), 2000)
|
||||
return () => clearInterval(timer)
|
||||
}, [ddo, did, config.metadataCacheUri])
|
||||
|
||||
return did && metadata ? (
|
||||
<Layout title={title} uri={uri}>
|
||||
|
@ -18,8 +18,7 @@ const AssetModel: MetadataMarket = {
|
||||
links: undefined,
|
||||
|
||||
// custom items
|
||||
termsAndConditions: false,
|
||||
priceType: undefined
|
||||
termsAndConditions: false
|
||||
}
|
||||
}
|
||||
|
||||
|
24
src/models/FormPricing.ts
Normal file
24
src/models/FormPricing.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { PriceOptionsMarket } from '../@types/MetaData'
|
||||
import * as Yup from 'yup'
|
||||
|
||||
export const validationSchema = Yup.object().shape<PriceOptionsMarket>({
|
||||
price: Yup.number().min(1, 'Must be greater than 0').required('Required'),
|
||||
dtAmount: Yup.number().min(1, 'Must be greater than 0').required('Required'),
|
||||
type: Yup.string()
|
||||
.matches(/fixed|dynamic/g)
|
||||
.required('Required'),
|
||||
weightOnDataToken: Yup.string().required('Required'),
|
||||
swapFee: Yup.number()
|
||||
.min(0.1, 'Must be more or equal to 0.1')
|
||||
.max(10, 'Maximum is 10%')
|
||||
.required('Required')
|
||||
.nullable()
|
||||
})
|
||||
|
||||
export const initialValues: PriceOptionsMarket = {
|
||||
price: 1,
|
||||
type: 'dynamic',
|
||||
dtAmount: 1,
|
||||
weightOnDataToken: '9', // 90% on data token
|
||||
swapFee: 0.1 // in %
|
||||
}
|
@ -6,26 +6,10 @@ export const validationSchema = Yup.object().shape<MetadataPublishForm>({
|
||||
// ---- required fields ----
|
||||
name: Yup.string().required('Required'),
|
||||
author: Yup.string().required('Required'),
|
||||
price: Yup.object()
|
||||
dataTokenOptions: Yup.object()
|
||||
.shape({
|
||||
price: Yup.number().min(1, 'Must be greater than 0').required('Required'),
|
||||
tokensToMint: Yup.number()
|
||||
.min(1, 'Must be greater than 0')
|
||||
.required('Required'),
|
||||
type: Yup.string()
|
||||
.matches(/fixed|dynamic/g)
|
||||
.required('Required'),
|
||||
weightOnDataToken: Yup.string().required('Required'),
|
||||
swapFee: Yup.number()
|
||||
.min(0.1, 'Must be more or equal to 0.1')
|
||||
.max(0.9, 'Must be less or equal to 0.9')
|
||||
.required('Required'),
|
||||
datatoken: Yup.object()
|
||||
.shape({
|
||||
name: Yup.string(),
|
||||
symbol: Yup.string()
|
||||
})
|
||||
.nullable()
|
||||
name: Yup.string(),
|
||||
symbol: Yup.string()
|
||||
})
|
||||
.required('Required'),
|
||||
files: Yup.array<FileMetadata>().required('Required').nullable(),
|
||||
@ -45,12 +29,9 @@ export const validationSchema = Yup.object().shape<MetadataPublishForm>({
|
||||
export const initialValues: Partial<MetadataPublishForm> = {
|
||||
name: '',
|
||||
author: '',
|
||||
price: {
|
||||
price: 1,
|
||||
type: 'dynamic',
|
||||
tokensToMint: 1,
|
||||
weightOnDataToken: '9', // 90% on data token
|
||||
swapFee: 0.1 // in %
|
||||
dataTokenOptions: {
|
||||
name: '',
|
||||
symbol: ''
|
||||
},
|
||||
files: '',
|
||||
description: '',
|
||||
|
@ -4,12 +4,9 @@ const testFormData: MetadataPublishForm = {
|
||||
author: '',
|
||||
files: [],
|
||||
license: '',
|
||||
price: {
|
||||
price: 1,
|
||||
tokensToMint: 9,
|
||||
type: 'fixed',
|
||||
weightOnDataToken: '1',
|
||||
swapFee: 0.1
|
||||
dataTokenOptions: {
|
||||
name: '',
|
||||
symbol: ''
|
||||
},
|
||||
name: '',
|
||||
description: 'description',
|
||||
|
@ -5,7 +5,7 @@ import {
|
||||
MetadataMarket,
|
||||
MetadataPublishForm
|
||||
} from '../../../src/@types/MetaData'
|
||||
import PublishForm from '../../../src/components/pages/Publish/PublishForm'
|
||||
import PublishForm from '../../../src/components/pages/Publish/FormPublish'
|
||||
import publishFormData from '../__fixtures__/testFormData'
|
||||
import content from '../../../content/pages/publish.json'
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user