mirror of
https://github.com/oceanprotocol/market.git
synced 2024-12-02 05:57:29 +01:00
Merge branch 'main' into feature/history-compute
This commit is contained in:
commit
c51ebb1dbe
@ -2,6 +2,5 @@
|
||||
GATSBY_NETWORK="rinkeby"
|
||||
|
||||
#GATSBY_INFURA_PROJECT_ID="xxx"
|
||||
#GATSBY_METADATA_STORE_URI="xxx"
|
||||
#GATSBY_METADATA_CACHE_URI="xxx"
|
||||
#GATSBY_MARKET_FEE_ADDRESS="0xxx"
|
||||
#GATSBY_MARKET_FEE_AMOUNT="xxx"
|
@ -1,11 +1,11 @@
|
||||
module.exports = {
|
||||
network: process.env.GATSBY_NETWORK || 'rinkeby',
|
||||
infuraProjectId: process.env.GATSBY_INFURA_PROJECT_ID || 'xxx',
|
||||
metadataStoreUri: process.env.GATSBY_METADATA_STORE_URI,
|
||||
metadataCacheUri: process.env.GATSBY_METADATA_CACHE_URI,
|
||||
// The ETH address the marketplace fee will be sent to.
|
||||
marketFeeAddress:
|
||||
process.env.GATSBY_MARKET_FEE_ADDRESS ||
|
||||
'0x903322C7E45A60d7c8C3EA236c5beA9Af86310c7',
|
||||
marketFeeAmount: process.env.GATSBY_MARKET_FEE_AMOUNT || '0.1', // in %
|
||||
// Used for conversion display, can be whatever coingecko API supports
|
||||
// see: https://api.coingecko.com/api/v3/simple/supported_vs_currencies
|
||||
currencies: ['EUR', 'USD', 'ETH', 'BTC']
|
||||
|
@ -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..."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
50
content/price.json
Normal file
50
content/price.json
Normal file
@ -0,0 +1,50 @@
|
||||
{
|
||||
"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...",
|
||||
"liquidity": "Explain what this represents, advantage of providing liquidity..."
|
||||
},
|
||||
"add": {
|
||||
"title": "Add Liquidity",
|
||||
"output": {
|
||||
"titleIn": "You will receive",
|
||||
"titleOut": "You will earn"
|
||||
},
|
||||
"action": "Approve & Supply"
|
||||
},
|
||||
"remove": {
|
||||
"title": "Remove Liquidity",
|
||||
"simple": "Set the amount of your pool shares to spend. You will get the equivalent value in OCEAN, limited to maximum amount for pool protection. If you have Datatokens left in your wallet, you can add them to the pool to increase the maximum amount.",
|
||||
"advanced": "Set the amount of your pool shares to spend. You will get OCEAN and Datatokens equivalent to your pool share, without any limit. You can use these Datatokens in other DeFi tools.",
|
||||
"output": {
|
||||
"titleIn": "You will spend",
|
||||
"titleOut": "You will receive"
|
||||
},
|
||||
"action": "Approve & Remove"
|
||||
}
|
||||
}
|
||||
}
|
5372
package-lock.json
generated
5372
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
76
package.json
76
package.json
@ -22,15 +22,15 @@
|
||||
"@coingecko/cryptoformat": "^0.4.2",
|
||||
"@loadable/component": "5.13.1",
|
||||
"@oceanprotocol/art": "^3.0.0",
|
||||
"@oceanprotocol/lib": "^0.5.6",
|
||||
"@oceanprotocol/react": "^0.1.2",
|
||||
"@oceanprotocol/lib": "^0.7.3",
|
||||
"@oceanprotocol/react": "^0.3.4",
|
||||
"@oceanprotocol/typographies": "^0.1.0",
|
||||
"@sindresorhus/slugify": "^1.0.0",
|
||||
"@tippyjs/react": "^4.2.0",
|
||||
"@toruslabs/torus-embed": "^1.8.5",
|
||||
"@toruslabs/torus-embed": "^1.8.6",
|
||||
"@types/classnames": "^2.2.10",
|
||||
"@vercel/node": "^1.8.3",
|
||||
"@walletconnect/web3-provider": "^1.2.2",
|
||||
"@vercel/node": "^1.8.4",
|
||||
"@walletconnect/web3-provider": "^1.3.1",
|
||||
"axios": "^0.20.0",
|
||||
"classnames": "^2.2.6",
|
||||
"date-fns": "^2.16.1",
|
||||
@ -39,78 +39,78 @@
|
||||
"dotenv": "^8.2.0",
|
||||
"ethereum-blockies": "github:MyEtherWallet/blockies",
|
||||
"filesize": "^6.1.0",
|
||||
"formik": "^2.1.7",
|
||||
"gatsby": "^2.24.67",
|
||||
"gatsby-image": "^2.4.20",
|
||||
"gatsby-plugin-manifest": "^2.4.33",
|
||||
"gatsby-plugin-react-helmet": "^3.3.12",
|
||||
"formik": "^2.2.0",
|
||||
"gatsby": "^2.24.85",
|
||||
"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.38",
|
||||
"gatsby-plugin-sharp": "^2.6.43",
|
||||
"gatsby-plugin-svgr": "^2.0.2",
|
||||
"gatsby-plugin-webpack-size": "^1.0.0",
|
||||
"gatsby-source-filesystem": "^2.3.32",
|
||||
"gatsby-source-graphql": "^2.7.5",
|
||||
"gatsby-transformer-json": "^2.4.13",
|
||||
"gatsby-transformer-remark": "^2.8.37",
|
||||
"gatsby-transformer-sharp": "^2.5.16",
|
||||
"gatsby-source-filesystem": "^2.3.35",
|
||||
"gatsby-source-graphql": "^2.7.6",
|
||||
"gatsby-transformer-json": "^2.4.15",
|
||||
"gatsby-transformer-remark": "^2.8.46",
|
||||
"gatsby-transformer-sharp": "^2.5.19",
|
||||
"intersection-observer": "^0.11.0",
|
||||
"is-url-superb": "^4.0.0",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"lodash.omit": "^4.5.0",
|
||||
"query-string": "^6.13.5",
|
||||
"react": "^16.13.1",
|
||||
"query-string": "^6.13.6",
|
||||
"react": "^16.14.0",
|
||||
"react-data-table-component": "^6.11.5",
|
||||
"react-datepicker": "^3.2.2",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-datepicker": "^3.3.0",
|
||||
"react-dom": "^16.14.0",
|
||||
"react-dotdotdot": "^1.3.1",
|
||||
"react-dropzone": "^11.2.0",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-markdown": "^4.3.1",
|
||||
"react-markdown": "^5.0.1",
|
||||
"react-paginate": "^6.5.0",
|
||||
"react-responsive-modal": "^5.1.1",
|
||||
"react-spring": "^8.0.27",
|
||||
"react-tabs": "^3.1.1",
|
||||
"react-toastify": "^6.0.8",
|
||||
"react-toastify": "^6.0.9",
|
||||
"remove-markdown": "^0.3.0",
|
||||
"shortid": "^2.2.15",
|
||||
"shortid": "^2.2.16",
|
||||
"slugify": "^1.4.5",
|
||||
"swr": "^0.3.5",
|
||||
"swr": "^0.3.6",
|
||||
"yup": "^0.29.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.11.6",
|
||||
"@babel/preset-typescript": "^7.10.1",
|
||||
"@babel/core": "^7.12.3",
|
||||
"@babel/preset-typescript": "^7.12.1",
|
||||
"@storybook/addon-actions": "^6.0.26",
|
||||
"@storybook/addon-storyshots": "^6.0.26",
|
||||
"@storybook/react": "^6.0.26",
|
||||
"@svgr/webpack": "^5.4.0",
|
||||
"@testing-library/jest-dom": "^5.11.4",
|
||||
"@testing-library/react": "^11.0.4",
|
||||
"@types/jest": "^26.0.14",
|
||||
"@testing-library/react": "^11.1.0",
|
||||
"@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.2",
|
||||
"@types/react": "^16.9.50",
|
||||
"@types/node": "^14.14.2",
|
||||
"@types/react": "^16.9.53",
|
||||
"@types/react-datepicker": "^3.1.1",
|
||||
"@types/react-helmet": "^6.1.0",
|
||||
"@types/react-paginate": "^6.2.1",
|
||||
"@types/react-tabs": "^2.3.2",
|
||||
"@types/remove-markdown": "^0.1.1",
|
||||
"@types/shortid": "0.0.29",
|
||||
"@types/yup": "^0.29.7",
|
||||
"@typescript-eslint/eslint-plugin": "^4.3.0",
|
||||
"@typescript-eslint/parser": "^4.3.0",
|
||||
"@types/yup": "^0.29.8",
|
||||
"@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.10.0",
|
||||
"eslint": "^7.11.0",
|
||||
"eslint-config-oceanprotocol": "^1.5.0",
|
||||
"eslint-config-prettier": "^6.12.0",
|
||||
"eslint-config-prettier": "^6.14.0",
|
||||
"eslint-plugin-prettier": "^3.1.4",
|
||||
"eslint-plugin-react": "^7.21.3",
|
||||
"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.0",
|
||||
"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>
|
||||
)
|
||||
}
|
||||
|
@ -11,7 +11,7 @@
|
||||
margin: 0;
|
||||
border-radius: var(--border-radius);
|
||||
transition: 0.2s ease-out;
|
||||
min-height: 43px;
|
||||
height: 43px;
|
||||
min-width: 0;
|
||||
appearance: none;
|
||||
display: block;
|
||||
@ -49,6 +49,11 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
.textarea {
|
||||
composes: input;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.select {
|
||||
composes: input;
|
||||
height: 43px;
|
||||
@ -179,15 +184,16 @@
|
||||
.prefix,
|
||||
.postfix {
|
||||
border: 1px solid var(--brand-grey-lighter);
|
||||
min-height: 43px;
|
||||
height: 43px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: calc(var(--spacer) / 4);
|
||||
padding-right: calc(var(--spacer) / 4);
|
||||
color: var(--color-secondary);
|
||||
color: var(--brand-grey);
|
||||
font-size: var(--font-size-small);
|
||||
transition: border 0.2s ease-out;
|
||||
white-space: nowrap;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.prefix {
|
||||
@ -204,11 +210,39 @@
|
||||
border-color: var(--brand-grey);
|
||||
}
|
||||
|
||||
input[type='range'] {
|
||||
background: none;
|
||||
}
|
||||
|
||||
input[type='range']:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
input[type='range']::-webkit-slider-thumb,
|
||||
input[type='range']::-moz-range-thumb {
|
||||
appearance: none;
|
||||
background: var(--brand-gradient);
|
||||
border: 2px solid var(--brand-grey-lighter);
|
||||
width: var(--font-size-large);
|
||||
height: var(--font-size-large);
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 2px 9px 0 rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
input[type='range']::-webkit-slider-runnable-track,
|
||||
input[type='range']::-moz-range-track {
|
||||
background: var(--brand-grey-lighter);
|
||||
border-radius: var(--border-radius);
|
||||
height: 0.3rem;
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* Size modifiers */
|
||||
|
||||
.small {
|
||||
font-size: var(--font-size-small);
|
||||
min-height: 34px;
|
||||
height: 34px;
|
||||
padding: calc(var(--spacer) / 4);
|
||||
}
|
||||
|
||||
@ -218,7 +252,7 @@
|
||||
|
||||
.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,
|
||||
@ -57,7 +57,12 @@ export default function InputElement({
|
||||
)
|
||||
case 'textarea':
|
||||
return (
|
||||
<textarea name={name} id={name} className={styles.input} {...props} />
|
||||
<textarea
|
||||
name={name}
|
||||
id={name}
|
||||
className={styles.textarea}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
case 'radio':
|
||||
case 'checkbox':
|
||||
@ -82,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:
|
||||
|
@ -13,12 +13,18 @@
|
||||
}
|
||||
|
||||
.error {
|
||||
font-size: var(--font-size-small);
|
||||
color: var(--brand-alert-red);
|
||||
display: inline-block;
|
||||
font-size: var(--font-size-mini);
|
||||
line-height: 1.2;
|
||||
font-weight: var(--font-weight-bold);
|
||||
color: var(--brand-white);
|
||||
background: var(--brand-alert-red);
|
||||
border-radius: var(--border-radius);
|
||||
padding: 0.2rem 0.4rem;
|
||||
position: absolute;
|
||||
text-align: right;
|
||||
right: 0;
|
||||
top: 0;
|
||||
top: 85%;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.hasError label {
|
||||
@ -26,7 +32,10 @@
|
||||
}
|
||||
|
||||
.hasError input,
|
||||
.hasError input:focus,
|
||||
.hasError select,
|
||||
.hasError textarea {
|
||||
.hasError textarea,
|
||||
.hasError [class*='prefix'],
|
||||
.hasError [class*='postfix'] {
|
||||
border-color: var(--brand-alert-red);
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ import InputElement from './InputElement'
|
||||
import Help from './Help'
|
||||
import Label from './Label'
|
||||
import styles from './index.module.css'
|
||||
import { ErrorMessage } from 'formik'
|
||||
import { ErrorMessage, FieldInputProps } from 'formik'
|
||||
import classNames from 'classnames/bind'
|
||||
|
||||
const cx = classNames.bind(styles)
|
||||
@ -33,7 +33,7 @@ export interface InputProps {
|
||||
max?: string
|
||||
disabled?: boolean
|
||||
readOnly?: boolean
|
||||
field?: any
|
||||
field?: FieldInputProps<any>
|
||||
form?: any
|
||||
prefix?: string | ReactElement
|
||||
postfix?: string | ReactElement
|
||||
@ -71,7 +71,7 @@ export default function Input(props: Partial<InputProps>): ReactElement {
|
||||
</Label>
|
||||
<InputElement small={small} {...field} {...props} />
|
||||
|
||||
{field && field.name !== 'price' && (
|
||||
{field && field.name !== 'price' && hasError && (
|
||||
<div className={styles.error}>
|
||||
<ErrorMessage name={field.name} />
|
||||
</div>
|
||||
|
@ -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);
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
import styles from './index.module.css'
|
||||
import { useMetadata, useOcean } from '@oceanprotocol/react'
|
||||
import { useMetadata } from '@oceanprotocol/react'
|
||||
import { DDO } from '@oceanprotocol/lib'
|
||||
import Loader from '../Loader'
|
||||
import Tooltip from '../Tooltip'
|
||||
|
4
src/components/atoms/SuccessConfetti.module.css
Normal file
4
src/components/atoms/SuccessConfetti.module.css
Normal file
@ -0,0 +1,4 @@
|
||||
.action {
|
||||
text-align: center;
|
||||
display: block;
|
||||
}
|
@ -1,8 +1,7 @@
|
||||
import Alert from '../../atoms/Alert'
|
||||
import Button from '../../atoms/Button'
|
||||
import React, { ReactElement, useEffect } from 'react'
|
||||
import Alert from './Alert'
|
||||
import React, { ReactElement, ReactNode, useEffect } from 'react'
|
||||
import { confetti } from 'dom-confetti'
|
||||
import styles from './Success.module.css'
|
||||
import styles from './SuccessConfetti.module.css'
|
||||
|
||||
const confettiConfig = {
|
||||
angle: 90,
|
||||
@ -24,33 +23,29 @@ const confettiConfig = {
|
||||
]
|
||||
}
|
||||
|
||||
export default function Success({
|
||||
export default function SuccessConfetti({
|
||||
success,
|
||||
did
|
||||
action
|
||||
}: {
|
||||
success: string
|
||||
did: string
|
||||
action?: ReactNode
|
||||
}): ReactElement {
|
||||
// Have some confetti upon success
|
||||
useEffect(() => {
|
||||
if (!success || typeof window === 'undefined') return
|
||||
|
||||
const startElement: HTMLElement = document.querySelector('a[data-confetti]')
|
||||
const startElement: HTMLElement = document.querySelector(
|
||||
'span[data-confetti]'
|
||||
)
|
||||
confetti(startElement, confettiConfig)
|
||||
}, [success])
|
||||
|
||||
return (
|
||||
<>
|
||||
<Alert text={success} state="success" />
|
||||
<Button
|
||||
style="primary"
|
||||
size="small"
|
||||
href={`/asset/${did}`}
|
||||
className={styles.action}
|
||||
data-confetti
|
||||
>
|
||||
Go to data set →
|
||||
</Button>
|
||||
<span className={styles.action} data-confetti>
|
||||
{action}
|
||||
</span>
|
||||
</>
|
||||
)
|
||||
}
|
@ -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,22 +9,30 @@ import {
|
||||
computeOptions,
|
||||
useCompute,
|
||||
readFileContent,
|
||||
useOcean
|
||||
useOcean,
|
||||
usePricing
|
||||
} from '@oceanprotocol/react'
|
||||
import styles from './Compute.module.css'
|
||||
import Button from '../../atoms/Button'
|
||||
import Input from '../../atoms/Input'
|
||||
import Alert from '../../atoms/Alert'
|
||||
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 { buyDT, dtSymbol } = usePricing(ddo)
|
||||
|
||||
const computeService = ddo.findServiceByType('compute')
|
||||
const metadataService = ddo.findServiceByType('metadata')
|
||||
|
||||
@ -44,6 +52,7 @@ export default function Compute({
|
||||
computeType === '' ||
|
||||
!ocean ||
|
||||
!isBalanceSufficient
|
||||
const hasDatatoken = Number(dtBalance) >= 1
|
||||
|
||||
const onDrop = async (files: File[]) => {
|
||||
setFile(files[0])
|
||||
@ -68,12 +77,15 @@ export default function Compute({
|
||||
setIsPublished(false)
|
||||
setError('')
|
||||
|
||||
!hasDatatoken && (await buyDT('1'))
|
||||
|
||||
await compute(
|
||||
ddo.id,
|
||||
computeService,
|
||||
ddo.dataToken,
|
||||
algorithmRawCode,
|
||||
computeContainer
|
||||
computeContainer,
|
||||
marketFeeAddress
|
||||
)
|
||||
|
||||
setIsPublished(true)
|
||||
@ -94,6 +106,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>
|
||||
|
||||
@ -115,7 +133,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,37 +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')}
|
||||
disabled={isDisabled}
|
||||
>
|
||||
Buy
|
||||
<Button style="primary" onClick={handleConsume} disabled={isDisabled}>
|
||||
{hasDatatoken ? 'Download' : 'Buy'}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
@ -51,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,9 +1,9 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
import Loader from '../../../atoms/Loader'
|
||||
import Button from '../../../atoms/Button'
|
||||
import Alert from '../../../atoms/Alert'
|
||||
import styles from './Actions.module.css'
|
||||
import EtherscanLink from '../../../atoms/EtherscanLink'
|
||||
import SuccessConfetti from '../../../atoms/SuccessConfetti'
|
||||
|
||||
export default function Actions({
|
||||
isLoading,
|
||||
@ -30,15 +30,14 @@ export default function Actions({
|
||||
)}
|
||||
</div>
|
||||
{txId && (
|
||||
<>
|
||||
<Alert
|
||||
text={`Successfully added liquidity. Transaction ID: ${txId}`}
|
||||
state="success"
|
||||
/>
|
||||
<EtherscanLink network="rinkeby" path={`/tx/${txId}`}>
|
||||
Etherscan
|
||||
</EtherscanLink>
|
||||
</>
|
||||
<SuccessConfetti
|
||||
success="Successfully added liquidity."
|
||||
action={
|
||||
<EtherscanLink network="rinkeby" path={`/tx/${txId}`}>
|
||||
See on Etherscan
|
||||
</EtherscanLink>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
|
@ -1,8 +1,8 @@
|
||||
.addInput {
|
||||
margin: 0 auto calc(var(--spacer) / 1.5) auto;
|
||||
background: var(--brand-grey-dimmed);
|
||||
padding: var(--spacer) calc(var(--spacer) * 3) calc(var(--spacer) * 1.2)
|
||||
calc(var(--spacer) * 3);
|
||||
padding: var(--spacer) calc(var(--spacer) * 2.5) calc(var(--spacer) * 1.2)
|
||||
calc(var(--spacer) * 2.5);
|
||||
border-bottom: 1px solid var(--brand-grey-lighter);
|
||||
margin-top: -2rem;
|
||||
margin-left: -2rem;
|
||||
@ -14,45 +14,34 @@
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.addInput div[class*='field'] {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.buttonMax {
|
||||
position: absolute;
|
||||
font-size: var(--font-size-mini);
|
||||
bottom: calc(var(--spacer) / 2);
|
||||
right: calc(var(--spacer) * 3);
|
||||
right: calc(var(--spacer) * 2.5);
|
||||
}
|
||||
|
||||
.userLiquidity {
|
||||
.userLiquidity > div {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: var(--font-size-mini);
|
||||
margin-bottom: calc(var(--spacer) / 4);
|
||||
color: var(--color-secondary);
|
||||
}
|
||||
|
||||
.userLiquidity > div:last-child {
|
||||
margin-bottom: calc(var(--spacer) / 4);
|
||||
}
|
||||
|
||||
.userLiquidity span + div {
|
||||
transform: scale(0.8);
|
||||
transform-origin: right center;
|
||||
}
|
||||
|
||||
.coinswitch,
|
||||
.coinPopover li {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.coinswitch svg {
|
||||
width: 0.6em;
|
||||
height: 0.6em;
|
||||
display: inline-block;
|
||||
fill: currentColor;
|
||||
margin-right: 0.5rem;
|
||||
margin-left: 0.25rem;
|
||||
}
|
||||
|
||||
.coinPopover li {
|
||||
padding: calc(var(--spacer) / 4) calc(var(--spacer) / 2);
|
||||
}
|
||||
|
||||
.output {
|
||||
display: grid;
|
||||
gap: var(--spacer);
|
||||
|
@ -1,17 +1,49 @@
|
||||
import React, { ReactElement, useState, ChangeEvent, useEffect } from 'react'
|
||||
import React, { ReactElement, useState, useEffect } from 'react'
|
||||
import styles from './Add.module.css'
|
||||
import { useOcean } from '@oceanprotocol/react'
|
||||
import Header from './Header'
|
||||
import { toast } from 'react-toastify'
|
||||
import InputElement from '../../../atoms/Input/InputElement'
|
||||
import Button from '../../../atoms/Button'
|
||||
import Token from './Token'
|
||||
import { Balance } from './'
|
||||
import PriceUnit from '../../../atoms/Price/PriceUnit'
|
||||
import Actions from './Actions'
|
||||
import { useUserPreferences } from '../../../../providers/UserPreferences'
|
||||
import Tooltip from '../../../atoms/Tooltip'
|
||||
import { ReactComponent as Caret } from '../../../../images/caret.svg'
|
||||
import { graphql, useStaticQuery } from 'gatsby'
|
||||
import * as Yup from 'yup'
|
||||
import { Field, FieldInputProps, Formik } from 'formik'
|
||||
import Input from '../../../atoms/Input'
|
||||
import CoinSelect from './CoinSelect'
|
||||
|
||||
const contentQuery = graphql`
|
||||
query PoolAddQuery {
|
||||
content: allFile(filter: { relativePath: { eq: "price.json" } }) {
|
||||
edges {
|
||||
node {
|
||||
childContentJson {
|
||||
pool {
|
||||
add {
|
||||
title
|
||||
output {
|
||||
titleIn
|
||||
titleOut
|
||||
}
|
||||
action
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
interface FormAddLiquidity {
|
||||
amount: number
|
||||
}
|
||||
|
||||
const initialValues: FormAddLiquidity = {
|
||||
amount: undefined
|
||||
}
|
||||
|
||||
export default function Add({
|
||||
setShowAdd,
|
||||
@ -30,133 +62,191 @@ export default function Add({
|
||||
dtSymbol: string
|
||||
dtAddress: string
|
||||
}): ReactElement {
|
||||
const { debug } = useUserPreferences()
|
||||
const data = useStaticQuery(contentQuery)
|
||||
const content = data.content.edges[0].node.childContentJson.pool.add
|
||||
|
||||
const { ocean, accountId, balance } = useOcean()
|
||||
const [amount, setAmount] = useState('')
|
||||
const [txId, setTxId] = useState<string>('')
|
||||
const [isLoading, setIsLoading] = useState<boolean>()
|
||||
const [coin, setCoin] = useState<string>('OCEAN')
|
||||
const [txId, setTxId] = useState<string>()
|
||||
const [coin, setCoin] = useState('OCEAN')
|
||||
const [dtBalance, setDtBalance] = useState<string>()
|
||||
const [amountMax, setAmountMax] = useState<string>()
|
||||
|
||||
const newPoolTokens =
|
||||
totalBalance &&
|
||||
((Number(amount) / Number(totalBalance.ocean)) * 100).toFixed(2)
|
||||
|
||||
const newPoolShare =
|
||||
totalBalance &&
|
||||
((Number(newPoolTokens) / Number(totalPoolTokens)) * 100).toFixed(2)
|
||||
// Live validation rules
|
||||
// https://github.com/jquense/yup#number
|
||||
const validationSchema = Yup.object().shape<FormAddLiquidity>({
|
||||
amount: Yup.number()
|
||||
.min(1, 'Must be more or equal to 1')
|
||||
.max(
|
||||
Number(amountMax),
|
||||
`Maximum you can add is ${Number(amountMax).toFixed(2)} ${coin}`
|
||||
)
|
||||
.required('Required')
|
||||
})
|
||||
|
||||
// Get datatoken balance when datatoken selected
|
||||
useEffect(() => {
|
||||
if (!ocean) return
|
||||
if (!ocean || coin === 'OCEAN') return
|
||||
|
||||
async function getDtBalance() {
|
||||
const dtBalance = await ocean.datatokens.balance(dtAddress, accountId)
|
||||
setDtBalance(dtBalance)
|
||||
}
|
||||
getDtBalance()
|
||||
}, [ocean, accountId, dtAddress])
|
||||
}, [ocean, accountId, dtAddress, coin])
|
||||
|
||||
async function handleAddLiquidity() {
|
||||
setIsLoading(true)
|
||||
// Get maximum amount for either OCEAN or datatoken
|
||||
useEffect(() => {
|
||||
if (!ocean) return
|
||||
|
||||
async function getMaximum() {
|
||||
const amountMaxPool =
|
||||
coin === 'OCEAN'
|
||||
? await ocean.pool.getOceanMaxAddLiquidity(poolAddress)
|
||||
: await ocean.pool.getDTMaxAddLiquidity(poolAddress)
|
||||
|
||||
const amountMax =
|
||||
coin === 'OCEAN'
|
||||
? Number(balance.ocean) > Number(amountMaxPool)
|
||||
? amountMaxPool
|
||||
: balance.ocean
|
||||
: Number(dtBalance) > Number(amountMaxPool)
|
||||
? amountMaxPool
|
||||
: dtBalance
|
||||
setAmountMax(amountMax)
|
||||
}
|
||||
getMaximum()
|
||||
}, [ocean, poolAddress, coin, dtBalance, balance.ocean])
|
||||
|
||||
// Submit
|
||||
async function handleAddLiquidity(amount: number, resetForm: () => void) {
|
||||
try {
|
||||
const result =
|
||||
coin === 'OCEAN'
|
||||
? await ocean.pool.addOceanLiquidity(accountId, poolAddress, amount)
|
||||
: await ocean.pool.addDTLiquidity(accountId, poolAddress, amount)
|
||||
? await ocean.pool.addOceanLiquidity(
|
||||
accountId,
|
||||
poolAddress,
|
||||
`${amount}`
|
||||
)
|
||||
: await ocean.pool.addDTLiquidity(accountId, poolAddress, `${amount}`)
|
||||
|
||||
setTxId(result?.transactionHash)
|
||||
resetForm()
|
||||
} catch (error) {
|
||||
console.error(error.message)
|
||||
toast.error(error.message)
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
function handleAmountChange(e: ChangeEvent<HTMLInputElement>) {
|
||||
setAmount(e.target.value)
|
||||
}
|
||||
|
||||
function handleMax() {
|
||||
setAmount(coin === 'OCEAN' ? balance.ocean : dtBalance)
|
||||
}
|
||||
|
||||
// TODO: this is only a prototype and is an accessibility nightmare.
|
||||
// Needs to be refactored to either use custom select element instead of tippy.js,
|
||||
// or use <button> in this implementation.
|
||||
// Also needs to be closed when users click an option.
|
||||
const CoinSelect = () => (
|
||||
<ul className={styles.coinPopover}>
|
||||
<li onClick={() => setCoin('OCEAN')}>OCEAN</li>
|
||||
<li onClick={() => setCoin(dtSymbol)}>{dtSymbol}</li>
|
||||
</ul>
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header title="Add Liquidity" backAction={() => setShowAdd(false)} />
|
||||
<Header title={content.title} backAction={() => setShowAdd(false)} />
|
||||
|
||||
<div className={styles.addInput}>
|
||||
<div className={styles.userLiquidity}>
|
||||
<span>Available: </span>
|
||||
{coin === 'OCEAN' ? (
|
||||
<PriceUnit price={balance.ocean} symbol="OCEAN" small />
|
||||
) : (
|
||||
<PriceUnit price={dtBalance} symbol={dtSymbol} small />
|
||||
)}
|
||||
</div>
|
||||
<Formik
|
||||
initialValues={initialValues}
|
||||
validationSchema={validationSchema}
|
||||
onSubmit={async (values, { setSubmitting, resetForm }) => {
|
||||
await handleAddLiquidity(values.amount, resetForm)
|
||||
setSubmitting(false)
|
||||
}}
|
||||
>
|
||||
{({
|
||||
values,
|
||||
touched,
|
||||
setTouched,
|
||||
isSubmitting,
|
||||
setFieldValue,
|
||||
submitForm,
|
||||
handleChange
|
||||
}) => {
|
||||
const newPoolTokens =
|
||||
totalBalance &&
|
||||
((values.amount / Number(totalBalance.ocean)) * 100).toFixed(2)
|
||||
|
||||
<InputElement
|
||||
value={amount}
|
||||
name="coin"
|
||||
type="number"
|
||||
prefix={
|
||||
<Tooltip
|
||||
content={<CoinSelect />}
|
||||
trigger="click focus"
|
||||
className={styles.coinswitch}
|
||||
placement="bottom"
|
||||
>
|
||||
{coin}
|
||||
<Caret aria-hidden="true" />
|
||||
</Tooltip>
|
||||
}
|
||||
placeholder="0"
|
||||
onChange={handleAmountChange}
|
||||
/>
|
||||
const newPoolShare =
|
||||
totalBalance &&
|
||||
((Number(newPoolTokens) / Number(totalPoolTokens)) * 100).toFixed(2)
|
||||
|
||||
{(balance.ocean || dtBalance) > amount && (
|
||||
<Button
|
||||
className={styles.buttonMax}
|
||||
style="text"
|
||||
size="small"
|
||||
onClick={handleMax}
|
||||
>
|
||||
Use Max
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
return (
|
||||
<>
|
||||
<div className={styles.addInput}>
|
||||
<div className={styles.userLiquidity}>
|
||||
<div>
|
||||
<span>Available:</span>
|
||||
{coin === 'OCEAN' ? (
|
||||
<PriceUnit price={balance.ocean} symbol="OCEAN" small />
|
||||
) : (
|
||||
<PriceUnit price={dtBalance} symbol={dtSymbol} small />
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<span>Maximum:</span>
|
||||
<PriceUnit price={amountMax} symbol={coin} small />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.output}>
|
||||
<div>
|
||||
<p>You will receive</p>
|
||||
{debug === true && <Token symbol="BPT" balance={newPoolTokens} />}
|
||||
<Token symbol="% of pool" balance={newPoolShare} />
|
||||
</div>
|
||||
<div>
|
||||
<p>You will earn</p>
|
||||
<Token symbol="% swap fee" balance={swapFee} />
|
||||
</div>
|
||||
</div>
|
||||
<Field name="amount">
|
||||
{({
|
||||
field,
|
||||
form
|
||||
}: {
|
||||
field: FieldInputProps<FormAddLiquidity>
|
||||
form: any
|
||||
}) => (
|
||||
<Input
|
||||
type="number"
|
||||
max={amountMax}
|
||||
value={`${values.amount}`}
|
||||
prefix={
|
||||
<CoinSelect dtSymbol={dtSymbol} setCoin={setCoin} />
|
||||
}
|
||||
placeholder="0"
|
||||
field={field}
|
||||
form={form}
|
||||
onChange={(e) => {
|
||||
// Workaround so validation kicks in on first touch
|
||||
!touched?.amount && setTouched({ amount: true })
|
||||
handleChange(e)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
<Actions
|
||||
isLoading={isLoading}
|
||||
loaderMessage="Adding Liquidity..."
|
||||
actionName="Supply"
|
||||
action={handleAddLiquidity}
|
||||
txId={txId}
|
||||
/>
|
||||
{(Number(balance.ocean) || dtBalance) >
|
||||
(values.amount || 0) && (
|
||||
<Button
|
||||
className={styles.buttonMax}
|
||||
style="text"
|
||||
size="small"
|
||||
onClick={() => setFieldValue('amount', amountMax)}
|
||||
>
|
||||
Use Max
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className={styles.output}>
|
||||
<div>
|
||||
<p>{content.output.titleIn}</p>
|
||||
<Token symbol="pool shares" balance={newPoolTokens} />
|
||||
<Token symbol="% of pool" balance={newPoolShare} />
|
||||
</div>
|
||||
<div>
|
||||
<p>{content.output.titleOut}</p>
|
||||
<Token symbol="% swap fee" balance={swapFee} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Actions
|
||||
isLoading={isSubmitting}
|
||||
loaderMessage="Adding Liquidity..."
|
||||
actionName={content.action}
|
||||
action={submitForm}
|
||||
txId={txId}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}}
|
||||
</Formik>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
@ -0,0 +1,26 @@
|
||||
.coinSelect {
|
||||
composes: select from '../../../atoms/Input/InputElement.module.css';
|
||||
font-size: var(--font-size-small);
|
||||
font-weight: var(--font-weight-base);
|
||||
border: none;
|
||||
margin-left: -0.5rem;
|
||||
margin-right: -0.5rem;
|
||||
background-color: var(--brand-grey-dimmed);
|
||||
width: auto;
|
||||
padding: 0 1.25rem 0 0.25rem;
|
||||
height: 41px;
|
||||
text-align: center;
|
||||
|
||||
/* custom arrow, without the divider line */
|
||||
background-image: linear-gradient(
|
||||
45deg,
|
||||
transparent 50%,
|
||||
var(--brand-purple) 50%
|
||||
),
|
||||
linear-gradient(135deg, var(--brand-grey) 50%, transparent 50%);
|
||||
background-position: calc(100% - 14px) 1.2rem, calc(100% - 9px) 1.2rem, 100% 0;
|
||||
}
|
||||
|
||||
.option {
|
||||
color: var(--brand-grey-dark);
|
||||
}
|
24
src/components/organisms/AssetActions/Pool/CoinSelect.tsx
Normal file
24
src/components/organisms/AssetActions/Pool/CoinSelect.tsx
Normal file
@ -0,0 +1,24 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
import styles from './CoinSelect.module.css'
|
||||
|
||||
export default function CoinSelect({
|
||||
dtSymbol,
|
||||
setCoin
|
||||
}: {
|
||||
dtSymbol: string
|
||||
setCoin: (coin: string) => void
|
||||
}): ReactElement {
|
||||
return (
|
||||
<select
|
||||
className={styles.coinSelect}
|
||||
onChange={(e) => setCoin(e.target.value)}
|
||||
>
|
||||
<option className={styles.option} value="OCEAN">
|
||||
OCEAN
|
||||
</option>
|
||||
<option className={styles.option} value={dtSymbol}>
|
||||
{dtSymbol}
|
||||
</option>
|
||||
</select>
|
||||
)
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
.statistics {
|
||||
}
|
||||
|
||||
.title {
|
||||
composes: title from './index.module.css';
|
||||
}
|
||||
|
||||
.totalLiquidity {
|
||||
composes: totalLiquidity from './index.module.css';
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
import { useUserPreferences } from '../../../../providers/UserPreferences'
|
||||
import React, { ReactElement } from 'react'
|
||||
import { Balance } from '.'
|
||||
import styles from './PoolStatistics.module.css'
|
||||
import Token from './Token'
|
||||
import Conversion from '../../../atoms/Price/Conversion'
|
||||
|
||||
export default function PoolStatistics({
|
||||
price,
|
||||
dtSymbol,
|
||||
totalBalance,
|
||||
totalPoolTokens,
|
||||
swapFee
|
||||
}: {
|
||||
price: string
|
||||
dtSymbol: string
|
||||
totalBalance: Balance
|
||||
totalPoolTokens: string
|
||||
swapFee: string
|
||||
}): ReactElement {
|
||||
const { debug } = useUserPreferences()
|
||||
|
||||
const totalLiquidityInOcean =
|
||||
totalBalance.ocean + totalBalance.datatoken * Number(price)
|
||||
|
||||
return (
|
||||
<div className={styles.statistics}>
|
||||
<h3 className={styles.title}>Pool Statistics</h3>
|
||||
<Token symbol="OCEAN" balance={`${totalBalance.ocean}`} />
|
||||
<Token symbol={dtSymbol} balance={`${totalBalance.datatoken}`} />
|
||||
{debug === true && <Token symbol="BPT" balance={totalPoolTokens} />}
|
||||
<Conversion
|
||||
price={`${totalLiquidityInOcean}`}
|
||||
className={styles.totalLiquidity}
|
||||
/>
|
||||
<Token symbol="% swap fee" balance={swapFee} />
|
||||
</div>
|
||||
)
|
||||
}
|
@ -1,11 +1,71 @@
|
||||
.removeInput {
|
||||
composes: addInput from './Add.module.css';
|
||||
}
|
||||
|
||||
.buttonMax {
|
||||
composes: buttonMax from './Add.module.css';
|
||||
padding-left: calc(var(--spacer) * 2);
|
||||
padding-right: calc(var(--spacer) * 2);
|
||||
}
|
||||
|
||||
.userLiquidity {
|
||||
composes: userLiquidity from './Add.module.css';
|
||||
max-width: 12rem;
|
||||
margin-top: -1rem;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-bottom: calc(var(--spacer) / 2);
|
||||
}
|
||||
|
||||
.range {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.range h3 {
|
||||
margin-bottom: calc(var(--spacer) / 4);
|
||||
}
|
||||
|
||||
.range input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.range p {
|
||||
margin-bottom: 0;
|
||||
margin-left: -2rem;
|
||||
margin-right: -2rem;
|
||||
}
|
||||
|
||||
.range button {
|
||||
margin-top: calc(var(--spacer) / 4);
|
||||
margin-bottom: 0;
|
||||
position: absolute;
|
||||
bottom: 1rem;
|
||||
right: 2rem;
|
||||
font-size: var(--font-size-mini);
|
||||
}
|
||||
|
||||
.slider {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.maximum {
|
||||
position: absolute;
|
||||
right: -1.5rem;
|
||||
bottom: 1.5rem;
|
||||
font-size: var(--font-size-small);
|
||||
z-index: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.output {
|
||||
composes: output from './Add.module.css';
|
||||
}
|
||||
|
||||
.output [class*='token'] {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.output [class*='token'] > figure {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.output figure[class*='pool shares'] {
|
||||
display: none;
|
||||
}
|
||||
|
@ -1,42 +1,91 @@
|
||||
import React, { ReactElement, useState, ChangeEvent } from 'react'
|
||||
import React, {
|
||||
ReactElement,
|
||||
useState,
|
||||
ChangeEvent,
|
||||
useEffect,
|
||||
FormEvent
|
||||
} from 'react'
|
||||
import styles from './Remove.module.css'
|
||||
import { useOcean } from '@oceanprotocol/react'
|
||||
import Header from './Header'
|
||||
import { toast } from 'react-toastify'
|
||||
import InputElement from '../../../atoms/Input/InputElement'
|
||||
import Actions from './Actions'
|
||||
import { Logger } from '@oceanprotocol/lib'
|
||||
import Token from './Token'
|
||||
import FormHelp from '../../../atoms/Input/Help'
|
||||
import Button from '../../../atoms/Button'
|
||||
import { getMaxValuesRemove } from './utils'
|
||||
import { graphql, useStaticQuery } from 'gatsby'
|
||||
import PriceUnit from '../../../atoms/Price/PriceUnit'
|
||||
import { Balance } from '.'
|
||||
|
||||
const contentQuery = graphql`
|
||||
query PoolRemoveQuery {
|
||||
content: allFile(filter: { relativePath: { eq: "price.json" } }) {
|
||||
edges {
|
||||
node {
|
||||
childContentJson {
|
||||
pool {
|
||||
remove {
|
||||
title
|
||||
simple
|
||||
advanced
|
||||
output {
|
||||
titleIn
|
||||
titleOut
|
||||
}
|
||||
action
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export default function Remove({
|
||||
setShowRemove,
|
||||
poolAddress,
|
||||
totalPoolTokens,
|
||||
userLiquidity
|
||||
poolTokens,
|
||||
dtSymbol
|
||||
}: {
|
||||
setShowRemove: (show: boolean) => void
|
||||
poolAddress: string
|
||||
totalPoolTokens: string
|
||||
userLiquidity: Balance
|
||||
poolTokens: string
|
||||
dtSymbol: string
|
||||
}): ReactElement {
|
||||
const data = useStaticQuery(contentQuery)
|
||||
const content = data.content.edges[0].node.childContentJson.pool.remove
|
||||
|
||||
const { ocean, accountId } = useOcean()
|
||||
const [amount, setAmount] = useState('')
|
||||
const [amountPercent, setAmountPercent] = useState('0')
|
||||
const [amountMaxPercent, setAmountMaxPercent] = useState('100')
|
||||
const [amountPoolShares, setAmountPoolShares] = useState('0')
|
||||
const [amountOcean, setAmountOcean] = useState('0')
|
||||
const [amountDatatoken, setAmountDatatoken] = useState('0')
|
||||
const [isAdvanced, setIsAdvanced] = useState(false)
|
||||
const [isLoading, setIsLoading] = useState<boolean>()
|
||||
const [txId, setTxId] = useState<string>('')
|
||||
const [txId, setTxId] = useState<string>()
|
||||
|
||||
async function handleRemoveLiquidity() {
|
||||
setIsLoading(true)
|
||||
|
||||
try {
|
||||
const result = await ocean.pool.removeOceanLiquidity(
|
||||
accountId,
|
||||
poolAddress,
|
||||
amount,
|
||||
totalPoolTokens
|
||||
)
|
||||
setTxId(result.transactionHash)
|
||||
const result =
|
||||
isAdvanced === true
|
||||
? await ocean.pool.removePoolLiquidity(
|
||||
accountId,
|
||||
poolAddress,
|
||||
amountPoolShares
|
||||
)
|
||||
: await ocean.pool.removeOceanLiquidity(
|
||||
accountId,
|
||||
poolAddress,
|
||||
amountDatatoken,
|
||||
amountPoolShares
|
||||
)
|
||||
|
||||
setTxId(result?.transactionHash)
|
||||
} catch (error) {
|
||||
Logger.error(error.message)
|
||||
toast.error(error.message)
|
||||
@ -45,55 +94,103 @@ export default function Remove({
|
||||
}
|
||||
}
|
||||
|
||||
function handleAmountChange(e: ChangeEvent<HTMLInputElement>) {
|
||||
setAmount(e.target.value)
|
||||
function handleAmountPercentChange(e: ChangeEvent<HTMLInputElement>) {
|
||||
setAmountPercent(e.target.value)
|
||||
}
|
||||
|
||||
function handleMax() {
|
||||
setAmount(`${userLiquidity.ocean}`)
|
||||
function handleAdvancedButton(e: FormEvent<HTMLButtonElement>) {
|
||||
e.preventDefault()
|
||||
setIsAdvanced(!isAdvanced)
|
||||
}
|
||||
|
||||
// Check and set outputs when percentage changes
|
||||
useEffect(() => {
|
||||
if (!ocean || !poolTokens) return
|
||||
|
||||
async function getValues() {
|
||||
const amountPoolShares =
|
||||
(Number(amountPercent) / 100) * Number(poolTokens)
|
||||
setAmountPoolShares(`${amountPoolShares}`)
|
||||
|
||||
if (isAdvanced === true) {
|
||||
setAmountMaxPercent('100')
|
||||
|
||||
const tokens = await ocean.pool.getTokensRemovedforPoolShares(
|
||||
poolAddress,
|
||||
`${amountPoolShares}`
|
||||
)
|
||||
setAmountOcean(tokens?.oceanAmount)
|
||||
setAmountDatatoken(tokens?.dtAmount)
|
||||
} else {
|
||||
const { amountMaxPercent, amountOcean } = await getMaxValuesRemove(
|
||||
ocean,
|
||||
poolAddress,
|
||||
poolTokens,
|
||||
`${amountPoolShares}`
|
||||
)
|
||||
setAmountMaxPercent(amountMaxPercent)
|
||||
setAmountOcean(amountOcean)
|
||||
}
|
||||
}
|
||||
getValues()
|
||||
}, [amountPercent, isAdvanced, ocean, poolTokens, poolAddress])
|
||||
|
||||
return (
|
||||
<div className={styles.remove}>
|
||||
<Header
|
||||
title="Remove Liquidity"
|
||||
backAction={() => setShowRemove(false)}
|
||||
/>
|
||||
<Header title={content.title} backAction={() => setShowRemove(false)} />
|
||||
|
||||
<form className={styles.removeInput}>
|
||||
<div className={styles.userLiquidity}>
|
||||
<span>Your pool liquidity: </span>
|
||||
<PriceUnit price={`${userLiquidity.ocean}`} symbol="OCEAN" small />
|
||||
<div>
|
||||
<span>Available:</span>
|
||||
<PriceUnit price={poolTokens} symbol="pool shares" small />
|
||||
</div>
|
||||
</div>
|
||||
<InputElement
|
||||
value={amount}
|
||||
name="ocean"
|
||||
type="number"
|
||||
prefix="OCEAN"
|
||||
placeholder="0"
|
||||
onChange={handleAmountChange}
|
||||
/>
|
||||
|
||||
{userLiquidity.ocean > Number(amount) && (
|
||||
<Button
|
||||
className={styles.buttonMax}
|
||||
style="text"
|
||||
size="small"
|
||||
onClick={handleMax}
|
||||
>
|
||||
Use Max
|
||||
<div className={styles.range}>
|
||||
<h3>{amountPercent}%</h3>
|
||||
<div className={styles.slider}>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max={amountMaxPercent}
|
||||
value={amountPercent}
|
||||
onChange={handleAmountPercentChange}
|
||||
/>
|
||||
{isAdvanced === false && (
|
||||
<span
|
||||
className={styles.maximum}
|
||||
>{`${amountMaxPercent}% max.`}</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<FormHelp>
|
||||
{isAdvanced === true ? content.advanced : content.simple}
|
||||
</FormHelp>
|
||||
<Button style="text" size="small" onClick={handleAdvancedButton}>
|
||||
{isAdvanced === true ? 'Simple' : 'Advanced'}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{/* <Input name="dt" label={dtSymbol} type="number" placeholder="0" /> */}
|
||||
|
||||
<p>You will receive</p>
|
||||
<div className={styles.output}>
|
||||
<div>
|
||||
<p>{content.output.titleIn}</p>
|
||||
<Token symbol="pool shares" balance={amountPoolShares} noIcon />
|
||||
</div>
|
||||
<div>
|
||||
<p>{content.output.titleOut}</p>
|
||||
<Token symbol="OCEAN" balance={amountOcean} />
|
||||
{isAdvanced === true && (
|
||||
<Token symbol={dtSymbol} balance={amountDatatoken} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Actions
|
||||
isLoading={isLoading}
|
||||
loaderMessage="Removing Liquidity..."
|
||||
actionName="Remove"
|
||||
actionName={content.action}
|
||||
action={handleRemoveLiquidity}
|
||||
txId={txId}
|
||||
/>
|
||||
|
@ -1,6 +1,6 @@
|
||||
.token {
|
||||
font-weight: var(--font-weight-bold);
|
||||
margin-bottom: calc(var(--spacer) / 3);
|
||||
margin-bottom: calc(var(--spacer) / 4);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@ -17,7 +17,7 @@
|
||||
padding: 0.3rem;
|
||||
vertical-align: middle;
|
||||
margin-right: calc(var(--spacer) / 8);
|
||||
margin-top: -0.3rem;
|
||||
margin-top: -0.2rem;
|
||||
}
|
||||
|
||||
.icon svg {
|
||||
@ -30,7 +30,6 @@
|
||||
fill: var(--brand-violet);
|
||||
}
|
||||
|
||||
.icon[class*='%'],
|
||||
.icon[class*='BPT'] {
|
||||
.noIcon {
|
||||
opacity: 0;
|
||||
}
|
||||
|
@ -5,14 +5,18 @@ import PriceUnit from '../../../atoms/Price/PriceUnit'
|
||||
|
||||
export default function Token({
|
||||
symbol,
|
||||
balance
|
||||
balance,
|
||||
noIcon
|
||||
}: {
|
||||
symbol: string
|
||||
balance: string
|
||||
noIcon?: boolean
|
||||
}): ReactElement {
|
||||
return (
|
||||
<div className={styles.token}>
|
||||
<figure className={`${styles.icon} ${symbol}`}>
|
||||
<figure
|
||||
className={`${styles.icon} ${symbol} ${noIcon ? styles.noIcon : ''}`}
|
||||
>
|
||||
<Logo />
|
||||
</figure>
|
||||
<PriceUnit price={balance} symbol={symbol} small />
|
||||
|
@ -0,0 +1,47 @@
|
||||
.tokeninfo {
|
||||
padding-top: calc(var(--spacer) / 1.5);
|
||||
}
|
||||
|
||||
.tokens {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
|
||||
/* visually tweak the order so tokens are underneath each other */
|
||||
.tokens > *:nth-child(1) {
|
||||
grid-row: 1 / 2;
|
||||
}
|
||||
|
||||
.tokens > *:nth-child(2) {
|
||||
grid-row: 2 / 2;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: var(--font-size-base);
|
||||
margin-bottom: calc(var(--spacer) / 3);
|
||||
color: var(--color-secondary);
|
||||
}
|
||||
|
||||
.highlight {
|
||||
background: var(--brand-grey-dimmed);
|
||||
padding: calc(var(--spacer) / 1.5) var(--spacer) calc(var(--spacer) / 2)
|
||||
var(--spacer);
|
||||
border-bottom: 1px solid var(--brand-grey-lighter);
|
||||
margin-left: -2rem;
|
||||
margin-right: -2rem;
|
||||
}
|
||||
|
||||
.totalLiquidity {
|
||||
composes: token from './Token.module.css';
|
||||
margin-bottom: 0;
|
||||
font-weight: var(--font-weight-base) !important;
|
||||
font-size: var(--font-size-small);
|
||||
padding-left: var(--font-size-base);
|
||||
padding-top: calc(var(--spacer) / 10);
|
||||
}
|
||||
|
||||
.totalLiquidity strong {
|
||||
font-size: var(--font-size-base);
|
||||
color: var(--brand-grey-dark);
|
||||
line-height: 1;
|
||||
}
|
44
src/components/organisms/AssetActions/Pool/TokenList.tsx
Normal file
44
src/components/organisms/AssetActions/Pool/TokenList.tsx
Normal file
@ -0,0 +1,44 @@
|
||||
import Conversion from '../../../atoms/Price/Conversion'
|
||||
import React, { ReactElement, ReactNode } from 'react'
|
||||
import Token from './Token'
|
||||
import styles from './TokenList.module.css'
|
||||
|
||||
export default function TokenList({
|
||||
title,
|
||||
children,
|
||||
ocean,
|
||||
dt,
|
||||
dtSymbol,
|
||||
poolShares,
|
||||
conversion,
|
||||
highlight
|
||||
}: {
|
||||
title: string | ReactNode
|
||||
children: ReactNode
|
||||
ocean: string
|
||||
dt: string
|
||||
dtSymbol: string
|
||||
poolShares: string
|
||||
conversion: number
|
||||
highlight?: boolean
|
||||
}): ReactElement {
|
||||
return (
|
||||
<div className={`${styles.tokeninfo} ${highlight ? styles.highlight : ''}`}>
|
||||
<h3 className={styles.title}>{title}</h3>
|
||||
<div className={styles.tokens}>
|
||||
<Token symbol="OCEAN" balance={ocean} />
|
||||
<Token symbol={dtSymbol} balance={dt} />
|
||||
<Token symbol="pool shares" balance={poolShares} noIcon />
|
||||
|
||||
{children}
|
||||
|
||||
{conversion > 0 && (
|
||||
<Conversion
|
||||
price={`${conversion}`}
|
||||
className={styles.totalLiquidity}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
@ -1,12 +1,11 @@
|
||||
.dataToken {
|
||||
margin-bottom: var(--spacer);
|
||||
padding-bottom: calc(var(--spacer) / 1.5);
|
||||
font-size: var(--font-size-large);
|
||||
border-bottom: 1px solid var(--brand-grey-lighter);
|
||||
margin-left: -2rem;
|
||||
margin-right: -2rem;
|
||||
padding-left: var(--spacer);
|
||||
padding-right: var(--spacer);
|
||||
padding-left: calc(var(--spacer) / 1.5);
|
||||
padding-right: calc(var(--spacer) / 1.5);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@ -21,27 +20,3 @@
|
||||
margin-left: calc(var(--spacer) / 3);
|
||||
margin-right: calc(var(--spacer) / 3);
|
||||
}
|
||||
|
||||
.poolTokens {
|
||||
display: grid;
|
||||
gap: var(--spacer);
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: var(--font-size-base);
|
||||
margin-bottom: calc(var(--spacer) / 1.5);
|
||||
}
|
||||
|
||||
.totalLiquidity {
|
||||
composes: token from './Token.module.css';
|
||||
font-weight: var(--font-weight-base) !important;
|
||||
font-size: var(--font-size-small);
|
||||
padding-left: var(--font-size-base);
|
||||
}
|
||||
|
||||
.totalLiquidity strong {
|
||||
font-size: var(--font-size-base);
|
||||
color: var(--brand-grey-dark);
|
||||
line-height: 1;
|
||||
}
|
||||
|
@ -1,9 +1,8 @@
|
||||
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'
|
||||
import { useUserPreferences } from '../../../../providers/UserPreferences'
|
||||
import PriceUnit from '../../../atoms/Price/PriceUnit'
|
||||
import Loader from '../../../atoms/Loader'
|
||||
import Button from '../../../atoms/Button'
|
||||
@ -12,26 +11,44 @@ import Remove from './Remove'
|
||||
import Tooltip from '../../../atoms/Tooltip'
|
||||
import Conversion from '../../../atoms/Price/Conversion'
|
||||
import EtherscanLink from '../../../atoms/EtherscanLink'
|
||||
import PoolStatistics from './PoolStatistics'
|
||||
import Token from './Token'
|
||||
import TokenList from './TokenList'
|
||||
import { graphql, useStaticQuery } from 'gatsby'
|
||||
|
||||
export interface Balance {
|
||||
ocean: number
|
||||
datatoken: number
|
||||
}
|
||||
|
||||
/*
|
||||
TODO: create tooltip copy
|
||||
*/
|
||||
const contentQuery = graphql`
|
||||
query PoolQuery {
|
||||
content: allFile(filter: { relativePath: { eq: "price.json" } }) {
|
||||
edges {
|
||||
node {
|
||||
childContentJson {
|
||||
pool {
|
||||
tooltips {
|
||||
price
|
||||
liquidity
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export default function Pool({ ddo }: { ddo: DDO }): ReactElement {
|
||||
const { debug } = useUserPreferences()
|
||||
const data = useStaticQuery(contentQuery)
|
||||
const content = data.content.edges[0].node.childContentJson.pool
|
||||
|
||||
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>()
|
||||
|
||||
@ -39,6 +56,8 @@ export default function Pool({ ddo }: { ddo: DDO }): ReactElement {
|
||||
const [showRemove, setShowRemove] = useState(false)
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
|
||||
// TODO: put all these variables behind some useEffect
|
||||
// to prevent unneccessary updating on every render
|
||||
const hasAddedLiquidity =
|
||||
userLiquidity && (userLiquidity.ocean > 0 || userLiquidity.datatoken > 0)
|
||||
|
||||
@ -51,6 +70,8 @@ export default function Pool({ ddo }: { ddo: DDO }): ReactElement {
|
||||
const totalUserLiquidityInOcean =
|
||||
userLiquidity?.ocean + userLiquidity?.datatoken * price?.value
|
||||
|
||||
const totalLiquidityInOcean = price?.ocean + price?.datatoken * price?.value
|
||||
|
||||
useEffect(() => {
|
||||
if (!ocean || !accountId || !price || !price.value) return
|
||||
|
||||
@ -58,16 +79,12 @@ 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
|
||||
//
|
||||
const totalPoolTokens = await ocean.pool.totalSupply(price.address)
|
||||
const totalPoolTokens = await ocean.pool.getPoolSharesTotalSupply(
|
||||
price.address
|
||||
)
|
||||
setTotalPoolTokens(totalPoolTokens)
|
||||
|
||||
//
|
||||
@ -124,8 +141,8 @@ export default function Pool({ ddo }: { ddo: DDO }): ReactElement {
|
||||
<Remove
|
||||
setShowRemove={setShowRemove}
|
||||
poolAddress={price.address}
|
||||
totalPoolTokens={totalPoolTokens}
|
||||
userLiquidity={userLiquidity}
|
||||
poolTokens={poolTokens}
|
||||
dtSymbol={dtSymbol}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
@ -133,7 +150,7 @@ export default function Pool({ ddo }: { ddo: DDO }): ReactElement {
|
||||
<PriceUnit price="1" symbol={dtSymbol} /> ={' '}
|
||||
<PriceUnit price={`${price.value}`} />
|
||||
<Conversion price={`${price.value}`} />
|
||||
<Tooltip content="Explain how this price is determined..." />
|
||||
<Tooltip content={content.tooltips.price} />
|
||||
<div className={styles.dataTokenLinks}>
|
||||
<EtherscanLink
|
||||
network="rinkeby"
|
||||
@ -147,30 +164,33 @@ export default function Pool({ ddo }: { ddo: DDO }): ReactElement {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.poolTokens}>
|
||||
<div className={styles.tokens}>
|
||||
<h3 className={styles.title}>
|
||||
<TokenList
|
||||
title={
|
||||
<>
|
||||
Your Liquidity
|
||||
<Tooltip content="Explain what this represents, advantage of providing liquidity..." />
|
||||
</h3>
|
||||
<Token symbol="OCEAN" balance={`${userLiquidity.ocean}`} />
|
||||
<Token symbol={dtSymbol} balance={`${userLiquidity.datatoken}`} />
|
||||
{debug === true && <Token symbol="BPT" balance={poolTokens} />}
|
||||
<Conversion
|
||||
price={`${totalUserLiquidityInOcean}`}
|
||||
className={styles.totalLiquidity}
|
||||
/>
|
||||
<Token symbol="% of pool" balance={poolShare} />
|
||||
</div>
|
||||
<Tooltip content={content.tooltips.liquidity} />
|
||||
</>
|
||||
}
|
||||
ocean={`${userLiquidity.ocean}`}
|
||||
dt={`${userLiquidity.datatoken}`}
|
||||
dtSymbol={dtSymbol}
|
||||
poolShares={poolTokens}
|
||||
conversion={totalUserLiquidityInOcean}
|
||||
highlight
|
||||
>
|
||||
<Token symbol="% of pool" balance={poolShare} noIcon />
|
||||
</TokenList>
|
||||
|
||||
<PoolStatistics
|
||||
price={`${price.value}`}
|
||||
totalPoolTokens={totalPoolTokens}
|
||||
totalBalance={{ ocean: price.ocean, datatoken: price.datatoken }}
|
||||
swapFee={swapFee}
|
||||
dtSymbol={dtSymbol}
|
||||
/>
|
||||
</div>
|
||||
<TokenList
|
||||
title="Pool Statistics"
|
||||
ocean={`${price.ocean}`}
|
||||
dt={`${price.datatoken}`}
|
||||
dtSymbol={dtSymbol}
|
||||
poolShares={totalPoolTokens}
|
||||
conversion={totalLiquidityInOcean}
|
||||
>
|
||||
<Token symbol="% swap fee" balance={swapFee} noIcon />
|
||||
</TokenList>
|
||||
|
||||
<div className={stylesActions.actions}>
|
||||
<Button
|
||||
|
28
src/components/organisms/AssetActions/Pool/utils.ts
Normal file
28
src/components/organisms/AssetActions/Pool/utils.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { Ocean } from '@oceanprotocol/lib'
|
||||
|
||||
export async function getMaxValuesRemove(
|
||||
ocean: Ocean,
|
||||
poolAddress: string,
|
||||
poolTokens: string,
|
||||
amountPoolShares: string
|
||||
): Promise<{ amountMaxPercent: string; amountOcean: string }> {
|
||||
const amountMaxOcean = await ocean.pool.getOceanMaxRemoveLiquidity(
|
||||
poolAddress
|
||||
)
|
||||
|
||||
const amountMaxPoolShares = await ocean.pool.getPoolSharesRequiredToRemoveOcean(
|
||||
poolAddress,
|
||||
amountMaxOcean
|
||||
)
|
||||
|
||||
const amountMaxPercent = `${Math.floor(
|
||||
(Number(amountMaxPoolShares) / Number(poolTokens)) * 100
|
||||
)}`
|
||||
|
||||
const amountOcean = await ocean.pool.getOceanRemovedforPoolShares(
|
||||
poolAddress,
|
||||
amountPoolShares
|
||||
)
|
||||
|
||||
return { amountMaxPercent, amountOcean }
|
||||
}
|
@ -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' && (
|
@ -11,18 +11,18 @@
|
||||
|
||||
.balance {
|
||||
text-align: center;
|
||||
font-size: var(--font-size-small);
|
||||
font-size: var(--font-size-small) !important;
|
||||
border: 1px solid var(--brand-grey-lighter);
|
||||
border-right: 0;
|
||||
margin-right: -3px;
|
||||
padding: calc(var(--spacer) / 4.5) calc(var(--spacer) / 2);
|
||||
height: 35px;
|
||||
padding: calc(var(--spacer) / 3) calc(var(--spacer) / 2)
|
||||
calc(var(--spacer) / 4.5) calc(var(--spacer) / 2);
|
||||
border-top-left-radius: var(--border-radius);
|
||||
border-bottom-left-radius: var(--border-radius);
|
||||
color: var(--color-secondary);
|
||||
}
|
||||
|
||||
.balance strong {
|
||||
color: var(--brand-grey);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.title {
|
||||
@ -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,33 +1,34 @@
|
||||
import { DataTokenOptions, useOcean } from '@oceanprotocol/react'
|
||||
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)
|
||||
@ -41,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(() => {
|
||||
@ -58,7 +59,7 @@ export default function Dynamic({
|
||||
return () => {
|
||||
clearInterval(balanceInterval)
|
||||
}
|
||||
}, [ocean, networkId, account])
|
||||
}, [networkId, account])
|
||||
|
||||
return (
|
||||
<div className={styles.dynamic}>
|
||||
@ -66,9 +67,12 @@ export default function Dynamic({
|
||||
|
||||
<aside className={styles.wallet}>
|
||||
{balance?.ocean && (
|
||||
<div className={styles.balance}>
|
||||
OCEAN <strong>{balance.ocean}</strong>
|
||||
</div>
|
||||
<PriceUnit
|
||||
className={styles.balance}
|
||||
price={balance.ocean}
|
||||
symbol="OCEAN"
|
||||
small
|
||||
/>
|
||||
)}
|
||||
<Wallet />
|
||||
</aside>
|
||||
@ -79,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={appConfig.marketFeeAmount}
|
||||
<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} />
|
||||
|
@ -1,6 +1,6 @@
|
||||
import AssetTeaser from '../molecules/AssetTeaser'
|
||||
import React from 'react'
|
||||
import { QueryResult } from '@oceanprotocol/lib/dist/node/metadatastore/MetadataStore'
|
||||
import { QueryResult } from '@oceanprotocol/lib/dist/node/metadatacache/MetadataCache'
|
||||
import { useLocation, useNavigate } from '@reach/router'
|
||||
import Pagination from '../molecules/Pagination'
|
||||
import { updateQueryStringParameter } from '../../utils'
|
||||
@ -57,7 +57,7 @@ const AssetList: React.FC<AssetListProps> = ({ queryResult }) => {
|
||||
})
|
||||
) : (
|
||||
<div className={styles.empty}>
|
||||
No results found in {config.metadataStoreUri}
|
||||
No results found in {config.metadataCacheUri}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Logger } from '@oceanprotocol/lib'
|
||||
import { QueryResult } from '@oceanprotocol/lib/dist/node/metadatastore/MetadataStore'
|
||||
import { QueryResult } from '@oceanprotocol/lib/dist/node/metadatacache/MetadataCache'
|
||||
import { useOcean } from '@oceanprotocol/react'
|
||||
import React, { ReactElement, useEffect, useState } from 'react'
|
||||
import Loader from '../../atoms/Loader'
|
||||
|
@ -1,12 +1,12 @@
|
||||
import React, { ReactElement, useEffect, useState } from 'react'
|
||||
import SearchBar from '../molecules/SearchBar'
|
||||
import styles from './Home.module.css'
|
||||
import { MetadataStore, Logger } from '@oceanprotocol/lib'
|
||||
import { MetadataCache, Logger } from '@oceanprotocol/lib'
|
||||
import AssetList from '../organisms/AssetList'
|
||||
import {
|
||||
QueryResult,
|
||||
SearchQuery
|
||||
} from '@oceanprotocol/lib/dist/node/metadatastore/MetadataStore'
|
||||
} from '@oceanprotocol/lib/dist/node/metadatacache/MetadataCache'
|
||||
import Container from '../atoms/Container'
|
||||
import Loader from '../atoms/Loader'
|
||||
import { useOcean } from '@oceanprotocol/react'
|
||||
@ -15,7 +15,14 @@ const queryHighest = {
|
||||
page: 1,
|
||||
offset: 3,
|
||||
query: { 'price.type': ['pool'] },
|
||||
sort: { 'price.value': 1 }
|
||||
sort: { 'price.ocean': -1 }
|
||||
}
|
||||
|
||||
const queryPoolsLatest = {
|
||||
page: 1,
|
||||
offset: 6,
|
||||
query: { 'price.type': ['pool'] },
|
||||
sort: { created: -1 }
|
||||
}
|
||||
|
||||
const queryLatest = {
|
||||
@ -25,10 +32,10 @@ const queryLatest = {
|
||||
sort: { created: -1 }
|
||||
}
|
||||
|
||||
async function getAssets(query: SearchQuery, metadataStoreUri: string) {
|
||||
async function getAssets(query: SearchQuery, metadataCacheUri: string) {
|
||||
try {
|
||||
const metadataStore = new MetadataStore(metadataStoreUri, Logger)
|
||||
const result = await metadataStore.queryMetadata(query)
|
||||
const metadataCache = new MetadataCache(metadataCacheUri, Logger)
|
||||
const result = await metadataCache.queryMetadata(query)
|
||||
|
||||
return result
|
||||
} catch (error) {
|
||||
@ -39,6 +46,9 @@ async function getAssets(query: SearchQuery, metadataStoreUri: string) {
|
||||
export default function HomePage(): ReactElement {
|
||||
const { config } = useOcean()
|
||||
const [queryResultLatest, setQueryResultLatest] = useState<QueryResult>()
|
||||
const [queryResultPoolsLatest, setQueryResultPoolsLatest] = useState<
|
||||
QueryResult
|
||||
>()
|
||||
const [queryResultHighest, setQueryResultHighest] = useState<QueryResult>()
|
||||
const [loading, setLoading] = useState(true)
|
||||
|
||||
@ -46,19 +56,25 @@ export default function HomePage(): ReactElement {
|
||||
async function init() {
|
||||
const queryResultHighest = await getAssets(
|
||||
queryHighest,
|
||||
config.metadataStoreUri
|
||||
config.metadataCacheUri
|
||||
)
|
||||
setQueryResultHighest(queryResultHighest)
|
||||
|
||||
const queryResultPoolsLatest = await getAssets(
|
||||
queryPoolsLatest,
|
||||
config.metadataCacheUri
|
||||
)
|
||||
setQueryResultPoolsLatest(queryResultPoolsLatest)
|
||||
|
||||
const queryResultLatest = await getAssets(
|
||||
queryLatest,
|
||||
config.metadataStoreUri
|
||||
config.metadataCacheUri
|
||||
)
|
||||
setQueryResultLatest(queryResultLatest)
|
||||
setLoading(false)
|
||||
}
|
||||
init()
|
||||
}, [config.metadataStoreUri])
|
||||
}, [config.metadataCacheUri])
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -67,7 +83,7 @@ export default function HomePage(): ReactElement {
|
||||
</Container>
|
||||
|
||||
<section className={styles.latest}>
|
||||
<h3>Highest Liquidity</h3>
|
||||
<h3>Highest Liquidity Pools</h3>
|
||||
{loading ? (
|
||||
<Loader />
|
||||
) : (
|
||||
@ -76,7 +92,18 @@ export default function HomePage(): ReactElement {
|
||||
</section>
|
||||
|
||||
<section className={styles.latest}>
|
||||
<h3>Latest</h3>
|
||||
<h3>New Liquidity Pools</h3>
|
||||
{loading ? (
|
||||
<Loader />
|
||||
) : (
|
||||
queryResultPoolsLatest && (
|
||||
<AssetList queryResult={queryResultPoolsLatest} />
|
||||
)
|
||||
)}
|
||||
</section>
|
||||
|
||||
<section className={styles.latest}>
|
||||
<h3>New Data Sets</h3>
|
||||
{loading ? (
|
||||
<Loader />
|
||||
) : (
|
||||
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
@ -1,23 +1,35 @@
|
||||
import Alert from '../../atoms/Alert'
|
||||
import Success from './Success'
|
||||
import Button from '../../atoms/Button'
|
||||
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 {
|
||||
const SuccessAction = () => (
|
||||
<Button
|
||||
style="primary"
|
||||
size="small"
|
||||
to={`/asset/${ddo?.id}`}
|
||||
className={styles.action}
|
||||
>
|
||||
Go to data set →
|
||||
</Button>
|
||||
)
|
||||
|
||||
return (
|
||||
<div className={styles.feedback}>
|
||||
<div className={styles.box}>
|
||||
@ -35,7 +47,7 @@ export default function Feedback({
|
||||
</Button>
|
||||
</>
|
||||
) : success ? (
|
||||
<Success success={success} did={did} />
|
||||
<SuccessConfetti success={success} action={<SuccessAction />} />
|
||||
) : (
|
||||
<Loader message={publishStepText} />
|
||||
)}
|
||||
|
@ -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, Form, 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
|
@ -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 === ''
|
||||
)
|
||||
|
@ -1,3 +0,0 @@
|
||||
.action {
|
||||
margin-top: calc(var(--spacer) / 1.5);
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,7 @@ import { Router } from '@reach/router'
|
||||
import AssetContent from '../../components/organisms/AssetContent'
|
||||
import Layout from '../../components/Layout'
|
||||
import { MetadataMarket } from '../../@types/MetaData'
|
||||
import { MetadataStore, Logger, DDO } from '@oceanprotocol/lib'
|
||||
import { MetadataCache, Logger, DDO } from '@oceanprotocol/lib'
|
||||
import Alert from '../../components/atoms/Alert'
|
||||
import Loader from '../../components/atoms/Loader'
|
||||
import { useOcean } from '@oceanprotocol/react'
|
||||
@ -23,13 +23,17 @@ export default function PageTemplateAssetDetails({
|
||||
|
||||
useEffect(() => {
|
||||
async function init() {
|
||||
if (ddo) return
|
||||
|
||||
try {
|
||||
const metadataStore = new MetadataStore(config.metadataStoreUri, Logger)
|
||||
const ddo = await metadataStore.retrieveDDO(did)
|
||||
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 MetadataStore.')
|
||||
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.metadataStoreUri])
|
||||
|
||||
// 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}>
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React, { ReactElement, useState, useEffect } from 'react'
|
||||
import { QueryResult } from '@oceanprotocol/lib/dist/node/metadatastore/MetadataStore'
|
||||
import { QueryResult } from '@oceanprotocol/lib/dist/node/metadatacache/MetadataCache'
|
||||
import SearchBar from '../../molecules/SearchBar'
|
||||
import AssetList from '../../organisms/AssetList'
|
||||
import styles from './index.module.css'
|
||||
@ -28,12 +28,12 @@ export default function SearchPage({
|
||||
useEffect(() => {
|
||||
async function initSearch() {
|
||||
setLoading(true)
|
||||
const queryResult = await getResults(parsed, config.metadataStoreUri)
|
||||
const queryResult = await getResults(parsed, config.metadataCacheUri)
|
||||
setQueryResult(queryResult)
|
||||
setLoading(false)
|
||||
}
|
||||
initSearch()
|
||||
}, [text, tag, page, config.metadataStoreUri])
|
||||
}, [text, tag, page, config.metadataCacheUri])
|
||||
|
||||
return (
|
||||
<section className={styles.grid}>
|
||||
|
@ -1,8 +1,8 @@
|
||||
import {
|
||||
SearchQuery,
|
||||
QueryResult
|
||||
} from '@oceanprotocol/lib/dist/node/metadatastore/MetadataStore'
|
||||
import { MetadataStore, Logger } from '@oceanprotocol/lib'
|
||||
} from '@oceanprotocol/lib/dist/node/metadatacache/MetadataCache'
|
||||
import { MetadataCache, Logger } from '@oceanprotocol/lib'
|
||||
|
||||
export function getSearchQuery(
|
||||
page?: string | string[],
|
||||
@ -30,12 +30,12 @@ export function getSearchQuery(
|
||||
|
||||
export async function getResults(
|
||||
params: { text?: string; tag?: string; page?: string; offset?: string },
|
||||
metadataStoreUri: string
|
||||
metadataCacheUri: string
|
||||
): Promise<QueryResult> {
|
||||
const { text, tag, page, offset } = params
|
||||
|
||||
const metadataStore = new MetadataStore(metadataStoreUri, Logger)
|
||||
const queryResult = await metadataStore.queryMetadata(
|
||||
const metadataCache = new MetadataCache(metadataCacheUri, Logger)
|
||||
const queryResult = await metadataCache.queryMetadata(
|
||||
getSearchQuery(page, offset, text, tag)
|
||||
)
|
||||
|
||||
|
@ -5,7 +5,7 @@ import appConfig from '../../app.config'
|
||||
import { Logger } from '@oceanprotocol/lib'
|
||||
|
||||
export function NetworkMonitor(): ReactElement {
|
||||
const { metadataStoreUri } = appConfig
|
||||
const { metadataCacheUri } = appConfig
|
||||
const { connect, web3Provider } = useOcean()
|
||||
|
||||
useEffect(() => {
|
||||
@ -16,8 +16,8 @@ export function NetworkMonitor(): ReactElement {
|
||||
|
||||
const newConfig = {
|
||||
...initialConfig,
|
||||
// add metadataStoreUri only when defined
|
||||
...(metadataStoreUri && { metadataStoreUri })
|
||||
// add metadataCacheUri only when defined
|
||||
...(metadataCacheUri && { metadataCacheUri })
|
||||
}
|
||||
|
||||
try {
|
||||
@ -32,7 +32,7 @@ export function NetworkMonitor(): ReactElement {
|
||||
return () => {
|
||||
web3Provider.removeListener('chainChanged', handleNetworkChanged)
|
||||
}
|
||||
}, [web3Provider, connect, metadataStoreUri])
|
||||
}, [web3Provider, connect, metadataCacheUri])
|
||||
|
||||
return <></>
|
||||
}
|
||||
|
@ -24,13 +24,13 @@ export default function wrapRootElement({
|
||||
}: {
|
||||
element: ReactElement
|
||||
}): ReactElement {
|
||||
const { metadataStoreUri, network } = appConfig
|
||||
const { metadataCacheUri, network } = appConfig
|
||||
const oceanInitialConfig = getOceanConfig(network)
|
||||
|
||||
const initialConfig = {
|
||||
...oceanInitialConfig,
|
||||
// add metadataStoreUri only when defined
|
||||
...(metadataStoreUri && { metadataStoreUri })
|
||||
// add metadataCacheUri only when defined
|
||||
...(metadataCacheUri && { metadataCacheUri })
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -17,7 +17,6 @@ const query = graphql`
|
||||
infuraProjectId
|
||||
network
|
||||
marketFeeAddress
|
||||
marketFeeAmount
|
||||
currencies
|
||||
}
|
||||
}
|
||||
|
@ -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: '',
|
||||
|
@ -1,5 +1,6 @@
|
||||
import loadable from '@loadable/component'
|
||||
import { infuraProjectId, network } from '../../app.config'
|
||||
import WalletConnectProvider from '@walletconnect/web3-provider'
|
||||
// import Torus from '@toruslabs/torus-embed'
|
||||
|
||||
const web3ModalTheme = {
|
||||
background: 'var(--brand-white)',
|
||||
@ -9,28 +10,23 @@ const web3ModalTheme = {
|
||||
hover: 'var(--brand-grey-dimmed)'
|
||||
}
|
||||
|
||||
const WalletConnectProvider = loadable(
|
||||
() => import('@walletconnect/web3-provider') as any
|
||||
)
|
||||
const Torus = loadable(() => import('@toruslabs/torus-embed') as any)
|
||||
|
||||
const providerOptions = {
|
||||
walletconnect: {
|
||||
package: WalletConnectProvider,
|
||||
options: {
|
||||
infuraId: infuraProjectId
|
||||
}
|
||||
},
|
||||
torus: {
|
||||
package: Torus,
|
||||
options: {
|
||||
networkParams: {
|
||||
// host: oceanConfig.url // optional
|
||||
// chainId: 1337, // optional
|
||||
// networkId: 1337 // optional
|
||||
}
|
||||
}
|
||||
}
|
||||
// torus: {
|
||||
// package: Torus,
|
||||
// options: {
|
||||
// networkParams: {
|
||||
// // host: oceanConfig.url // optional
|
||||
// // chainId: 1337, // optional
|
||||
// // networkId: 1337 // optional
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
export const web3ModalOpts = {
|
||||
|
@ -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',
|
||||
|
@ -1,6 +1,6 @@
|
||||
import ddo from '../../__fixtures__/ddo'
|
||||
|
||||
const metadataStore = {
|
||||
const metadataCache = {
|
||||
queryMetadata: () => {
|
||||
return {
|
||||
results: [] as any[],
|
||||
@ -11,13 +11,13 @@ const metadataStore = {
|
||||
}
|
||||
|
||||
const libMock = {
|
||||
MetadataStore: () => metadataStore,
|
||||
MetadataStore: () => metadataCache,
|
||||
DDO: () => ddo,
|
||||
ocean: {
|
||||
accounts: {
|
||||
list: () => ['xxx', 'xxx']
|
||||
},
|
||||
metadataStore,
|
||||
metadataCache,
|
||||
// compute: {
|
||||
// status: (account: string) => {
|
||||
// return [job]
|
||||
|
@ -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