1
0
mirror of https://github.com/oceanprotocol/market.git synced 2024-09-28 03:58:59 +02:00

Merge pull request #37 from oceanprotocol/feature/price

Custom price input
This commit is contained in:
Matthias Kretschmann 2020-08-03 19:33:05 +02:00 committed by GitHub
commit 12d3b2f0a0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 737 additions and 122 deletions

View File

@ -35,11 +35,9 @@
"required": true
},
{
"name": "cost",
"name": "price",
"label": "Price",
"help": "Set your price for accessing this data set in Ocean Tokens.",
"type": "price",
"min": 1,
"required": true
},
{

View File

@ -5,12 +5,9 @@ import {
ServiceMetadata
} from '@oceanprotocol/lib'
export declare type AccessType = 'Download' | 'Compute'
export interface AdditionalInformationMarket extends AdditionalInformation {
links?: File[]
termsAndConditions: boolean
access: AccessType | string
}
export interface MetadataMarket extends Metadata {
@ -24,7 +21,10 @@ export interface MetadataPublishForm {
files: string | File[]
author: string
license: string
cost: string
price: {
cost: number
tokensToMint: number
}
access: 'Download' | 'Compute' | string
termsAndConditions: boolean
// ---- optional fields ----

View File

@ -1,9 +1,23 @@
import React, { ReactElement } from 'react'
import styles from './Help.module.css'
import Markdown from '../Markdown'
import classNames from 'classnames/bind'
const FormHelp = ({ children }: { children: string }): ReactElement => (
<Markdown className={styles.help} text={children} />
)
const cx = classNames.bind(styles)
const FormHelp = ({
children,
className
}: {
children: string
className?: string
}): ReactElement => {
const styleClasses = cx({
help: true,
[className]: className
})
return <Markdown className={styleClasses} text={children} />
}
export default FormHelp

View File

@ -12,6 +12,7 @@
border-radius: var(--border-radius);
transition: 0.2s ease-out;
min-height: 43px;
min-width: 0;
appearance: none;
display: block;
}
@ -33,7 +34,8 @@
.input[readonly],
.input[disabled] {
background-color: var(--brand-grey-lighter);
background-color: var(--brand-grey-dimmed);
color: var(--brand-grey-light);
cursor: not-allowed;
pointer-events: none;
}
@ -84,6 +86,52 @@
padding-left: 0.5rem;
}
.prefixGroup,
.postfixGroup {
display: inline-flex;
align-items: center;
}
.prefixGroup input {
border-left: 0;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
.postfixGroup input {
border-right: 0;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
.prefix,
.postfix {
border: 1px solid var(--brand-grey-lighter);
min-height: 43px;
display: flex;
align-items: center;
padding-left: calc(var(--spacer) / 4);
padding-right: calc(var(--spacer) / 4);
color: var(--color-secondary);
font-size: var(--font-size-small);
transition: border 0.2s ease-out;
white-space: nowrap;
}
.prefix {
border-top-left-radius: var(--border-radius);
border-bottom-left-radius: var(--border-radius);
}
.postfix {
border-top-right-radius: var(--border-radius);
border-bottom-right-radius: var(--border-radius);
}
.input:focus + .postfix {
border-color: var(--brand-grey);
}
/* Size modifiers */
.small {

View File

@ -4,9 +4,23 @@ 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'
const DefaultInput = (
{ name, type }: { name: string; type?: string },
props: InputProps
) => (
<input
id={name}
className={styles.input}
name={name}
{...props}
type={type || 'text'}
/>
)
export default function InputElement(props: InputProps): ReactElement {
const { type, options, rows, name, value } = props
const { type, options, rows, name, prefix, postfix } = props
switch (type) {
case 'select':
@ -56,18 +70,19 @@ export default function InputElement(props: InputProps): ReactElement {
)
case 'files':
return <FilesInput name={name} {...props} />
case 'price':
return <Price name={name} {...props} />
case 'terms':
return <Terms name={name} {...props} />
default:
return (
<input
id={name}
className={styles.input}
name={name}
{...props}
value={value || ''}
type={type || 'text'}
/>
return prefix || postfix ? (
<div className={`${prefix ? styles.prefixGroup : styles.postfixGroup}`}>
{prefix && <div className={styles.prefix}>{prefix}</div>}
<DefaultInput name={name} type={type || 'text'} />
{postfix && <div className={styles.postfix}>{postfix}</div>}
</div>
) : (
<DefaultInput name={name} type={type || 'text'} />
)
}
}

View File

@ -1,4 +1,4 @@
import React, { FormEvent, ChangeEvent, ReactElement } from 'react'
import React, { FormEvent, ChangeEvent, ReactElement, ReactNode } from 'react'
import InputElement from './InputElement'
import Help from './Help'
import Label from './Label'
@ -31,8 +31,12 @@ export interface InputProps {
pattern?: string
min?: string
disabled?: boolean
readOnly?: boolean
field?: any
form?: any
prefix?: string
postfix?: string
step?: string
}
export default function Input(props: Partial<InputProps>): ReactElement {

View File

@ -1,6 +1,6 @@
.conversion {
display: inline-block;
font-size: var(--font-size-mini);
font-size: var(--font-size-small);
margin-left: calc(var(--spacer) / 6);
color: var(--color-secondary);
}

View File

@ -2,19 +2,29 @@ import React, { useEffect, useState, ReactElement } from 'react'
import useSWR from 'swr'
import { fetchData, isBrowser } from '../../../utils'
import styles from './Conversion.module.css'
import classNames from 'classnames/bind'
const cx = classNames.bind(styles)
const currencies = 'EUR' // comma-separated list
const url = `https://api.coingecko.com/api/v3/simple/price?ids=ocean-protocol&vs_currencies=${currencies}&include_24hr_change=true`
export default function Conversion({
price,
update = true
update = true,
className
}: {
price: string // expects price in OCEAN, not wei
update?: boolean
className?: string
}): ReactElement {
const [priceEur, setPriceEur] = useState('0.00')
const styleClasses = cx({
conversion: true,
[className]: className
})
const onSuccess = async (data: { 'ocean-protocol': { eur: number } }) => {
if (!data) return
if (!price || price === '' || price === '0') {
@ -45,5 +55,12 @@ export default function Conversion({
})
}
return <span className={styles.conversion}> EUR {priceEur}</span>
return (
<span
className={styleClasses}
title="Approximation based on current spot price on Coingecko"
>
EUR {priceEur}
</span>
)
}

View File

@ -1,4 +1,4 @@
import React from 'react'
import React, { ReactElement } from 'react'
import DataTable from 'react-data-table-component'
export declare type AssetTablePagination = {

View File

@ -0,0 +1,26 @@
.tabList {
text-align: center;
border-bottom: 1px solid var(--brand-grey-lighter);
padding-top: calc(var(--spacer) / 2);
padding-bottom: calc(var(--spacer) / 2);
}
.tab {
display: inline-block;
padding: calc(var(--spacer) / 12) var(--spacer);
border-radius: var(--border-radius);
font-weight: var(--font-weight-bold);
font-size: var(--font-size-small);
text-transform: uppercase;
cursor: pointer;
color: var(--brand-grey-light);
}
.tab[aria-selected='true'] {
background: var(--brand-black);
color: var(--brand-white);
}
.tabContent {
padding: var(--spacer);
}

View File

@ -0,0 +1,33 @@
import React, { ReactElement, ReactNode } from 'react'
import { Tab, Tabs as ReactTabs, TabList, TabPanel } from 'react-tabs'
import styles from './Tabs.module.css'
interface TabsItem {
title: string
content: ReactNode
}
export default function Tabs({
items,
className
}: {
items: TabsItem[]
className?: string
}): ReactElement {
return (
<ReactTabs className={`${className && className}`}>
<TabList className={styles.tabList}>
{items.map((item) => (
<Tab className={styles.tab} key={item.title}>
{item.title}
</Tab>
))}
</TabList>
<div className={styles.tabContent}>
{items.map((item) => (
<TabPanel key={item.title}>{item.content}</TabPanel>
))}
</div>
</ReactTabs>
)
}

View File

@ -36,7 +36,7 @@ export default function FilesInput(props: InputProps): ReactElement {
return (
<>
{typeof field.value === 'object' ? (
{field && typeof field.value === 'object' ? (
<FileInfo file={field.value[0]} removeItem={removeItem} />
) : (
<FileInput

View File

@ -0,0 +1,56 @@
.advanced {
}
.wallet {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: calc(var(--spacer) / 2);
}
.balance {
text-align: center;
font-size: var(--font-size-small);
border: 1px solid var(--brand-grey-lighter);
border-right: 0;
margin-right: -3px;
padding: 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);
}
.title {
font-size: var(--font-size-base);
margin-top: var(--spacer);
margin-bottom: 0;
color: var(--color-secondary);
text-align: center;
padding-bottom: calc(var(--spacer) / 3);
}
.tokens {
display: grid;
margin-bottom: -2rem;
margin-left: -3rem;
margin-right: -3rem;
}
@media screen and (min-width: 40rem) {
.tokens {
grid-template-columns: 1fr 1fr;
}
}
.alertArea {
margin-left: -3rem;
margin-right: -3rem;
padding: var(--spacer) calc(var(--spacer) / 2);
padding-bottom: 0;
margin-top: var(--spacer);
border-top: 1px solid var(--brand-grey-lighter);
}

View File

@ -0,0 +1,89 @@
import React, { ReactElement, useState, ChangeEvent, useEffect } from 'react'
import stylesIndex from './index.module.css'
import styles from './Advanced.module.css'
import FormHelp from '../../../atoms/Input/Help'
import Wallet from '../../Wallet'
import { useOcean } from '@oceanprotocol/react'
import Alert from '../../../atoms/Alert'
import Coin from './Coin'
import { isCorrectNetwork } from '../../../../utils/wallet'
import { useSiteMetadata } from '../../../../hooks/useSiteMetadata'
export default function Advanced({
ocean,
tokensToMint,
weightOnDataToken,
onChange
}: {
ocean: string
tokensToMint: number
weightOnDataToken: string
onChange: (event: ChangeEvent<HTMLInputElement>) => void
}): ReactElement {
const { appConfig } = useSiteMetadata()
const { account, balance, chainId } = useOcean()
const [error, setError] = useState<string>()
const correctNetwork = isCorrectNetwork(chainId)
const desiredNetworkName = appConfig.network.replace(/^\w/, (c: string) =>
c.toUpperCase()
)
// Check: account, network & insuffciant balance
useEffect(() => {
if (!account) {
setError(`No account connected. Please connect your Web3 wallet.`)
} else if (!correctNetwork) {
setError(`Wrong Network. Please connect to ${desiredNetworkName}.`)
} else if (balance.ocean < ocean) {
setError(`Insufficiant balance. You need at least ${ocean} OCEAN`)
} else {
setError(undefined)
}
}, [ocean])
return (
<div className={stylesIndex.content}>
<div className={styles.advanced}>
<FormHelp className={stylesIndex.help}>
{`Let's create a decentralized, automated market for your data set. A Data Token contract for this data set worth the entered amount of OCEAN will be created. Additionally, you will provide liquidity into a Data Token/OCEAN
liquidity pool with Balancer.`}
</FormHelp>
<aside className={styles.wallet}>
{balance && balance.ocean && (
<div className={styles.balance}>
OCEAN <strong>{balance.ocean}</strong>
</div>
)}
<Wallet />
</aside>
<h4 className={styles.title}>Data Token Liquidity Pool</h4>
<div className={styles.tokens}>
<Coin
name="ocean"
symbol="OCEAN"
value={ocean}
weight={`${100 - Number(weightOnDataToken)}%`}
onChange={onChange}
/>
<Coin
name="tokensToMint"
symbol="OCEAN-CAV"
value={tokensToMint.toString()}
weight={`${weightOnDataToken}%`}
readOnly
/>
</div>
{error && (
<div className={styles.alertArea}>
<Alert text={error} state="error" />
</div>
)}
</div>
</div>
)
}

View File

@ -0,0 +1,59 @@
.coin {
padding: var(--spacer) calc(var(--spacer) / 2);
border-top: 1px solid var(--brand-grey-lighter);
}
.coin:last-child {
border-left: 1px solid var(--brand-grey-lighter);
}
.icon {
composes: box from '../../../atoms/Box.module.css';
padding: calc(var(--spacer) / 1.5);
width: 6rem;
height: 6rem;
display: flex;
align-items: center;
justify-content: center;
border: 1px solid var(--brand-grey-lighter);
border-radius: 50%;
background-color: var(--brand-white);
margin-bottom: var(--spacer);
}
.coin:last-child .icon path {
fill: var(--brand-grey-dimmed);
stroke: var(--brand-black);
stroke-width: 5px;
stroke-linejoin: round;
}
.data {
position: relative;
width: 100%;
display: flex;
flex-wrap: wrap;
justify-content: center;
text-align: center;
max-width: 12rem;
margin: auto;
}
.max {
position: absolute;
right: var(--spacer);
top: calc(var(--spacer) / 2.5);
}
.weight {
width: 100%;
margin-top: var(--spacer);
text-transform: uppercase;
font-size: var(--font-size-small);
color: var(--color-secondary);
}
.weight strong {
color: var(--brand-grey);
font-size: var(--font-size-base);
}

View File

@ -0,0 +1,46 @@
import React, { ReactElement, ChangeEvent } 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'
export default function Coin({
symbol,
name,
value,
weight,
onChange,
readOnly
}: {
symbol: string
name: string
value: string
weight: string
onChange?: (event: ChangeEvent<HTMLInputElement>) => void
readOnly?: boolean
}): ReactElement {
return (
<div className={styles.coin}>
<figure className={styles.icon}>
<Logo />
</figure>
<div className={styles.data}>
<InputElement
value={value}
name={name}
type="number"
onChange={onChange}
readOnly={readOnly}
prefix={symbol}
/>
<Conversion price={value} className={stylesIndex.conversion} />
<div className={styles.weight}>
Weight <strong>{weight}</strong>
</div>
</div>
</div>
)
}

View File

@ -0,0 +1,8 @@
.form {
max-width: 12rem;
margin: 0 auto;
}
.simple label {
display: none;
}

View File

@ -0,0 +1,40 @@
import React, { ReactElement, ChangeEvent } from 'react'
import stylesIndex from './index.module.css'
import styles from './Simple.module.css'
import FormHelp from '../../../atoms/Input/Help'
import Label from '../../../atoms/Input/Label'
import InputElement from '../../../atoms/Input/InputElement'
import Conversion from '../../../atoms/Price/Conversion'
export default function Simple({
ocean,
onChange
}: {
ocean: string
onChange: (event: ChangeEvent<HTMLInputElement>) => void
}): ReactElement {
return (
<div className={stylesIndex.content}>
<div className={styles.simple}>
<FormHelp className={stylesIndex.help}>
Set your price for accessing this data set. A Data Token contract for
this data set, worth the entered amount of OCEAN will be created.
</FormHelp>
<form className={styles.form}>
<Label htmlFor="ocean">Ocean Tokens</Label>
<InputElement
value={ocean}
name="ocean"
type="number"
prefix="OCEAN"
onChange={onChange}
/>
<Conversion price={ocean} className={stylesIndex.conversion} />
</form>
</div>
</div>
)
}

View File

@ -0,0 +1,33 @@
.price {
border: 1px solid var(--brand-grey-lighter);
background: var(--brand-grey-dimmed);
border-radius: var(--border-radius);
}
.content {
padding: 0 calc(var(--spacer) / 2);
}
.content label {
color: var(--color-secondary);
}
.content input {
text-align: center;
}
.content p {
margin-bottom: 0;
}
.conversion {
width: 100%;
display: block;
text-align: center;
margin-top: calc(var(--spacer) / 6);
}
.help {
text-align: center;
margin-bottom: calc(var(--spacer) / 1.5);
}

View File

@ -0,0 +1,54 @@
import React, { ReactElement, useState, ChangeEvent, useEffect } from 'react'
import { InputProps } from '../../../atoms/Input'
import styles from './index.module.css'
import Tabs from '../../../atoms/Tabs'
import Simple from './Simple'
import Advanced from './Advanced'
import { useField } from 'formik'
export default function Price(props: InputProps): ReactElement {
const [field, meta, helpers] = useField(props)
const cost = 1
const weightOnDataToken = '90' // in %
const [ocean, setOcean] = useState('1')
const [tokensToMint, setTokensToMint] = useState<number>()
function handleOceanChange(event: ChangeEvent<HTMLInputElement>) {
setOcean(event.target.value)
}
// Always update everything when ocean changes
useEffect(() => {
const tokensToMint = Number(ocean) * (Number(weightOnDataToken) / 10)
setTokensToMint(tokensToMint)
helpers.setValue({ cost, tokensToMint })
}, [ocean])
const tabs = [
{
title: 'Simple: Fixed',
content: <Simple ocean={ocean} onChange={handleOceanChange} />
},
{
title: 'Advanced: Dynamic',
content: (
<Advanced
ocean={ocean}
tokensToMint={tokensToMint}
weightOnDataToken={weightOnDataToken}
onChange={handleOceanChange}
/>
)
}
]
return (
<div className={styles.price}>
<Tabs items={tabs} />
<pre>
<code>{JSON.stringify(field.value)}</code>
</pre>
</div>
)
}

View File

@ -66,7 +66,7 @@
padding: calc(var(--spacer) / 2);
margin-left: var(--spacer);
text-transform: uppercase;
color: var(--brand-grey);
color: var(--brand-grey-light);
font-weight: var(--font-weight-bold);
font-size: var(--font-size-base);
position: relative;
@ -82,13 +82,13 @@
.link:hover,
.link:focus,
.link:active {
color: var(--brand-pink);
color: var(--brand-grey);
}
.link[aria-current],
.link[aria-current]:hover,
.link[aria-current]:focus {
color: var(--brand-pink);
color: var(--brand-black);
}
.link:last-child {

View File

@ -8,12 +8,16 @@
padding: calc(var(--spacer) / 4);
white-space: nowrap;
background: none;
color: var(--brand-grey);
margin: 0;
transition: border 0.2s ease-out;
cursor: pointer;
}
.button,
.address {
color: var(--brand-grey);
}
.button:hover,
.button:focus {
transform: none;
@ -21,6 +25,10 @@
border-color: var(--brand-grey-light);
}
.button.initial {
color: var(--brand-pink);
}
.blockies {
width: var(--font-size-large);
height: var(--font-size-large);

View File

@ -1,4 +1,4 @@
import React from 'react'
import React, { FormEvent } from 'react'
import styles from './Account.module.css'
import { useOcean } from '@oceanprotocol/react'
import { toDataUrl } from 'ethereum-blockies'
@ -26,8 +26,20 @@ const Account = React.forwardRef((props, ref: any) => {
const { accountId, status, connect, chainId } = useOcean()
const hasSuccess = status === 1 && isCorrectNetwork(chainId)
async function handleActivation(e: FormEvent<HTMLButtonElement>) {
// prevent accidentially submitting a form the button might be in
e.preventDefault()
await connect()
}
return accountId ? (
<button className={styles.button} aria-label="Account" ref={ref}>
<button
className={styles.button}
aria-label="Account"
ref={ref}
// prevent accidentially submitting a form the button might be in
onClick={(e) => e.preventDefault()}
>
<Blockies account={accountId} />
<span className={styles.address} title={accountId}>
{accountTruncate(accountId)}
@ -39,13 +51,13 @@ const Account = React.forwardRef((props, ref: any) => {
</button>
) : (
<button
className={styles.button}
onClick={async () => await connect()}
className={`${styles.button} ${styles.initial}`}
onClick={(e) => handleActivation(e)}
// Need the `ref` here although we do not want
// the Tippy to show in this state.
ref={ref}
>
Activate Wallet
Connect Wallet
</button>
)
})

View File

@ -21,7 +21,7 @@ export default function Web3Feedback({
const isOceanConnectionError = status === -1
const correctNetwork = isCorrectNetwork(chainId)
const showFeedback = !account || isOceanConnectionError || !correctNetwork
const networkName = appConfig.network.replace(/^\w/, (c: string) =>
const desiredNetworkName = appConfig.network.replace(/^\w/, (c: string) =>
c.toUpperCase()
)
@ -50,7 +50,7 @@ export default function Web3Feedback({
: isOceanConnectionError
? 'Please try again.'
: !correctNetwork
? `Please connect to ${networkName}.`
? `Please connect to ${desiredNetworkName}.`
: isBalanceInsufficient === true
? 'You do not have enough OCEAN in your wallet to purchase this asset.'
: 'Something went wrong.'

View File

@ -4,29 +4,3 @@
margin: auto;
padding: 0;
}
.tabList {
text-align: center;
border-bottom: 1px solid var(--brand-grey-lighter);
padding-top: calc(var(--spacer) / 2);
padding-bottom: calc(var(--spacer) / 2);
}
.tab {
display: inline-block;
padding: calc(var(--spacer) / 12) var(--spacer);
border-radius: var(--border-radius);
font-weight: var(--font-weight-bold);
text-transform: uppercase;
cursor: pointer;
color: var(--brand-grey-light);
}
.tab[aria-selected='true'] {
background: var(--brand-black);
color: var(--brand-white);
}
.tabContent {
padding: var(--spacer);
}

View File

@ -1,10 +1,10 @@
import React, { ReactElement } from 'react'
import { Tab, Tabs, TabList, TabPanel } from 'react-tabs'
import styles from './index.module.css'
import Compute from './Compute'
import Consume from './Consume'
import { MetadataMarket } from '../../../@types/Metadata'
import { DDO } from '@oceanprotocol/lib'
import Tabs from '../../atoms/Tabs'
export default function AssetActions({
metadata,
@ -15,23 +15,22 @@ export default function AssetActions({
}): ReactElement {
const { access } = metadata.additionalInformation
const isCompute = access && access === 'Compute'
return (
<Tabs className={styles.actions}>
<TabList className={styles.tabList}>
<Tab className={styles.tab}>Use</Tab>
<Tab className={styles.tab}>Trade</Tab>
</TabList>
<div className={styles.tabContent}>
<TabPanel>
{isCompute ? (
const UseContent = isCompute ? (
<Compute ddo={ddo} />
) : (
<Consume ddo={ddo} file={metadata.main.files[0]} />
)}
</TabPanel>
<TabPanel>Trade Me</TabPanel>
</div>
</Tabs>
)
const tabs = [
{
title: 'Use',
content: UseContent
},
{
title: 'Trade',
content: 'Trade Me'
}
]
return <Tabs items={tabs} className={styles.actions} />
}

View File

@ -51,6 +51,10 @@ export default function AssetContent({
/> */}
{/* <DeleteAction ddo={ddo} /> */}
</div>
<pre>
<code>{JSON.stringify(ddo, null, 2)}</code>
</pre>
</div>
<div>
<div className={styles.sticky}>

View File

@ -38,6 +38,7 @@ export default function Preview({
key.includes('tags') ||
key.includes('files') ||
key.includes('termsAndConditions') ||
key.includes('price') ||
value === undefined ||
value === ''
)

View File

@ -19,7 +19,6 @@ export default function PublishForm({
status,
setStatus,
isValid,
touched,
setErrors,
setTouched,
resetForm,
@ -42,7 +41,6 @@ export default function PublishForm({
{content.data.map((field: FormFieldProps) => (
<Field key={field.name} {...field} component={Input} />
))}
{isLoading ? (
<Loader message={publishStepText} />
) : (

View File

@ -7,7 +7,7 @@ import styles from './index.module.css'
import PublishForm from './PublishForm'
import Web3Feedback from '../../molecules/Wallet/Feedback'
import { FormContent } from '../../../@types/Form'
import { initialValues, validationSchema } from './validation'
import { initialValues, validationSchema } from '../../../models/FormPublish'
import { MetadataPublishForm } from '../../../@types/Metadata'
import { transformPublishFormToMetadata } from './utils'
import Preview from './Preview'
@ -28,20 +28,20 @@ export default function PublishPage({
`)
const metadata = transformPublishFormToMetadata(values)
const tokensToMint = '4' // how to know this?
const { cost, tokensToMint } = values.price
const serviceType = values.access === 'Download' ? 'access' : 'compute'
console.log(`
Transformed metadata values:
----------------------
${JSON.stringify(metadata, null, 2)}
Cost: 1
Cost: ${cost}
Tokens to mint: ${tokensToMint}
`)
try {
const ddo = await publish(metadata as any, tokensToMint, [
{ serviceType, cost: '1' }
const ddo = await publish(metadata as any, tokensToMint.toString(), [
{ serviceType, cost: cost.toString() }
])
if (publishError) {
@ -82,6 +82,26 @@ export default function PublishPage({
<Web3Feedback />
</div>
</aside>
<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>
</>
)}
</Formik>

View File

@ -16,8 +16,7 @@ export function transformPublishFormToMetadata(
tags,
links,
termsAndConditions,
files,
access
files
} = data
const metadata: MetadataMarket = {
@ -38,8 +37,7 @@ export function transformPublishFormToMetadata(
// links: {
// url: links
// },
termsAndConditions,
access: access || 'Download'
termsAndConditions
},
curation: AssetModel.curation
}

View File

@ -1,7 +1,7 @@
code {
font-family: var(--font-family-monospace);
font-size: var(--font-size-small);
color: var(--brand-grey-light);
color: var(--brand-grey);
text-shadow: none;
}
@ -14,13 +14,22 @@ code {
pre {
display: block;
margin: calc(var(--spacer) / 2) 0;
padding: 0;
border: 1px solid var(--brand-grey-dark);
padding: calc(var(--spacer) / 2);
background-color: var(--brand-grey-dimmed) !important;
border-radius: var(--border-radius);
overflow: auto;
-webkit-overflow-scrolling: touch;
max-height: 800px;
width: 100%;
position: relative;
}
pre code {
padding: calc(var(--spacer) / 2);
padding: 0;
white-space: pre;
display: block;
white-space: pre-wrap;
overflow-wrap: normal;
word-wrap: normal;
word-break: normal;
width: 100%;
}

View File

@ -132,6 +132,28 @@ fieldset {
margin: 0;
}
table {
width: 100%;
border-collapse: collapse;
display: block;
overflow: auto;
-webkit-overflow-scrolling: touch;
}
table th,
table td {
border: 0;
margin: 0;
padding: calc(var(--spacer) / 2);
border-bottom: 1px solid var(--brand-grey-lighter);
text-align: left;
font-size: 90%;
}
table th {
font-weight: var(--font-weight-bold);
}
@import '_code.css';
@import '_toast.css';
@import '_web3modal.css';

26
src/images/logo.svg Normal file
View File

@ -0,0 +1,26 @@
<svg width="394" height="399" viewBox="0 0 394 399" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M196.742 57.2631C212.525 57.2631 225.33 44.4482 225.33 28.6321C225.33 12.82 212.525 0 196.742 0C180.944 0 168.139 12.82 168.139 28.6321C168.139 44.4482 180.944 57.2631 196.742 57.2631Z" fill="#141414"/>
<path d="M29.167 213.019C40.8609 213.019 50.3274 203.527 50.3274 191.826C50.3274 180.125 40.8609 170.644 29.167 170.644C17.4833 170.644 8.00663 180.125 8.00663 191.826C8.00663 203.527 17.4833 213.019 29.167 213.019Z" fill="#141414"/>
<path d="M365.451 213.019C377.129 213.019 386.606 203.527 386.606 191.826C386.606 180.125 377.129 170.644 365.451 170.644C353.762 170.644 344.285 180.125 344.285 191.826C344.285 203.527 353.762 213.019 365.451 213.019Z" fill="#141414"/>
<path d="M218.467 275.433C218.467 287.132 208.991 296.623 197.309 296.623C185.622 296.623 176.146 287.132 176.146 275.433C176.146 263.733 185.622 254.248 197.309 254.248C208.991 254.248 218.467 263.733 218.467 275.433Z" fill="#141414"/>
<path d="M112.662 255.393C124.355 255.393 133.825 245.902 133.825 234.206C133.825 222.5 124.355 213.019 112.662 213.019C100.975 213.019 91.5044 222.5 91.5044 234.206C91.5044 245.902 100.975 255.393 112.662 255.393Z" fill="#141414"/>
<path d="M301.965 234.206C301.965 245.902 292.489 255.393 280.807 255.393C269.114 255.393 259.644 245.902 259.644 234.206C259.644 222.5 269.114 213.019 280.807 213.019C292.489 213.019 301.965 222.5 301.965 234.206Z" fill="#141414"/>
<path d="M29.1748 270.282C37.0689 270.282 43.4646 263.864 43.4646 255.971C43.4646 248.058 37.0689 241.65 29.1748 241.65C21.2703 241.65 14.8695 248.058 14.8695 255.971C14.8695 263.864 21.2703 270.282 29.1748 270.282Z" fill="#141414"/>
<path d="M379.743 255.971C379.743 263.864 373.338 270.282 365.446 270.282C357.548 270.282 351.148 263.864 351.148 255.971C351.148 248.058 357.548 241.65 365.446 241.65C373.338 241.65 379.743 248.058 379.743 255.971Z" fill="#141414"/>
<path d="M197.312 355.031C205.196 355.031 211.604 348.613 211.604 340.705C211.604 332.802 205.196 326.4 197.312 326.4C189.422 326.4 183.009 332.802 183.009 340.705C183.009 348.613 189.422 355.031 197.312 355.031Z" fill="#141414"/>
<path d="M126.962 298.346C126.962 306.251 120.563 312.656 112.67 312.656C104.772 312.656 98.3672 306.251 98.3672 298.346C98.3672 290.435 104.772 284.025 112.67 284.025C120.563 284.025 126.962 290.435 126.962 298.346Z" fill="#141414"/>
<path d="M280.812 312.656C288.693 312.656 295.102 306.251 295.102 298.346C295.102 290.435 288.693 284.025 280.812 284.025C272.91 284.025 266.507 290.435 266.507 298.346C266.507 306.251 272.91 312.656 280.812 312.656Z" fill="#141414"/>
<path d="M36.6018 306.35C36.6018 310.469 33.2752 313.802 29.1697 313.802C25.0588 313.802 21.7323 310.469 21.7323 306.35C21.7323 302.241 25.0588 298.913 29.1697 298.913C33.2752 298.913 36.6018 302.241 36.6018 306.35Z" fill="#141414"/>
<path d="M365.448 313.802C369.546 313.802 372.88 310.469 372.88 306.35C372.88 302.241 369.546 298.913 365.448 298.913C361.335 298.913 358.011 302.241 358.011 306.35C358.011 310.469 361.335 313.802 365.448 313.802Z" fill="#141414"/>
<path d="M204.741 391.102C204.741 395.214 201.407 398.551 197.309 398.551C193.201 398.551 189.872 395.214 189.872 391.102C189.872 386.989 193.201 383.663 197.309 383.663C201.407 383.663 204.741 386.989 204.741 391.102Z" fill="#141414"/>
<path d="M112.662 356.176C116.768 356.176 120.1 352.839 120.1 348.732C120.1 344.615 116.768 341.288 112.662 341.288C108.562 341.288 105.23 344.615 105.23 348.732C105.23 352.839 108.562 356.176 112.662 356.176Z" fill="#141414"/>
<path d="M288.239 348.732C288.239 352.839 284.905 356.176 280.807 356.176C276.688 356.176 273.369 352.839 273.369 348.732C273.369 344.615 276.688 341.288 280.807 341.288C284.905 341.288 288.239 344.615 288.239 348.732Z" fill="#141414"/>
<path d="M225.33 113.381C225.33 129.195 212.525 142.012 196.742 142.012C180.944 142.012 168.139 129.195 168.139 113.381C168.139 97.572 180.944 84.7494 196.742 84.7494C212.525 84.7494 225.33 97.572 225.33 113.381Z" fill="#141414"/>
<path d="M196.742 225.617C212.525 225.617 225.33 212.797 225.33 196.99C225.33 181.173 212.525 168.353 196.742 168.353C180.944 168.353 168.139 181.173 168.139 196.99C168.139 212.797 180.944 225.617 196.742 225.617Z" fill="#141414"/>
<path d="M393.469 113.381C393.469 129.195 380.667 142.012 364.876 142.012C349.081 142.012 336.279 129.195 336.279 113.381C336.279 97.572 349.081 84.7494 364.876 84.7494C380.667 84.7494 393.469 97.572 393.469 113.381Z" fill="#141414"/>
<path d="M28.5977 142.012C44.3907 142.012 57.1903 129.195 57.1903 113.381C57.1903 97.572 44.3907 84.7494 28.5977 84.7494C12.8046 84.7494 0 97.572 0 113.381C0 129.195 12.8046 142.012 28.5977 142.012Z" fill="#141414"/>
<path d="M141.832 71.0062C141.832 86.818 129.031 99.6378 113.237 99.6378C97.4422 99.6378 84.6416 86.818 84.6416 71.0062C84.6416 55.1842 97.4422 42.3747 113.237 42.3747C129.031 42.3747 141.832 55.1842 141.832 71.0062Z" fill="#141414"/>
<path d="M113.237 183.242C129.031 183.242 141.832 170.421 141.832 154.608C141.832 138.8 129.031 125.979 113.237 125.979C97.4422 125.979 84.6416 138.8 84.6416 154.608C84.6416 170.421 97.4422 183.242 113.237 183.242Z" fill="#141414"/>
<path d="M309.971 71.0062C309.971 86.818 297.167 99.6378 281.373 99.6378C265.58 99.6378 252.781 86.818 252.781 71.0062C252.781 55.1842 265.58 42.3747 281.373 42.3747C297.167 42.3747 309.971 55.1842 309.971 71.0062Z" fill="#141414"/>
<path d="M281.373 183.242C297.167 183.242 309.971 170.421 309.971 154.608C309.971 138.8 297.167 125.979 281.373 125.979C265.58 125.979 252.781 138.8 252.781 154.608C252.781 170.421 265.58 183.242 281.373 183.242Z" fill="#141414"/>
</svg>

After

Width:  |  Height:  |  Size: 5.5 KiB

View File

@ -5,21 +5,20 @@ const AssetModel: MetadataMarket = {
// https://github.com/oceanprotocol/OEPs/tree/master/8
main: {
type: 'dataset',
name: '',
dateCreated: '',
author: '',
license: '',
name: undefined,
dateCreated: undefined,
author: undefined,
license: undefined,
files: []
},
additionalInformation: {
description: '',
copyrightHolder: '',
tags: [],
links: [],
description: undefined,
copyrightHolder: undefined,
tags: undefined,
links: undefined,
// custom items
termsAndConditions: false,
access: 'Download'
termsAndConditions: false
},
curation: {
rating: 0,

View File

@ -1,4 +1,4 @@
import { MetadataPublishForm } from '../../../@types/Metadata'
import { MetadataPublishForm } from '../@types/Metadata'
import { File as FileMetadata } from '@oceanprotocol/lib'
import * as Yup from 'yup'
@ -6,7 +6,10 @@ export const validationSchema = Yup.object().shape<MetadataPublishForm>({
// ---- required fields ----
name: Yup.string().required('Required'),
author: Yup.string().required('Required'),
cost: Yup.string().required('Required'),
price: Yup.object().shape({
cost: Yup.number().required('Required'),
tokensToMint: Yup.number().required('Required')
}),
files: Yup.array<FileMetadata>().required('Required').nullable(),
description: Yup.string().required('Required'),
license: Yup.string().required('Required'),
@ -22,15 +25,18 @@ export const validationSchema = Yup.object().shape<MetadataPublishForm>({
})
export const initialValues: MetadataPublishForm = {
name: '',
author: '',
cost: '',
files: '',
description: '',
license: '',
access: '',
name: undefined,
author: undefined,
price: {
cost: 1,
tokensToMint: 1
},
files: undefined,
description: undefined,
license: undefined,
access: undefined,
termsAndConditions: false,
copyrightHolder: '',
tags: '',
links: ''
copyrightHolder: undefined,
tags: undefined,
links: undefined
}

View File

@ -32,7 +32,6 @@ export const contentQuery = graphql`
type
required
options
min
}
success
}